Method to allow functions to know the output vector size (ISAC IR2132, Bugzilla #240)


Proposal Details

  • Who Updates: Kevin Jennings
  • Date Proposed: Sep 2011 Bugzilla #240, Dec 2012 e-mail reflector, April 4, 2013 twiki
  • Date Last Updated: April 4, 2013
  • Priority:
  • Complexity:
  • Focus:
  • References: Bugzilla #240, ISAC IR2132

Current Situation

Functions that return arrays occasionally need knowledge of the array bounds to which they are going to be assigned in order to operate
properly. In order to provide this information today, a function must provide additional arguments that are typically attributes of the
signal/variable being assigned to.

One classic example involving numeric_std is:

x <= to_unsigned(1, x'length);

Another involving fixed_pkg is resize

x <= resize(a, x);

In both instances the argument 'x' is supplied as an input to a function solely for the purposes of allowing the function to have knowledge of some attribute(s) of the intended recipient of the function. In the case of 'to_unsigned' the 'length attribute is needed; in the case of 'resize' the 'left and 'right attributes are needed.

When working with real designs this can lead to latent design errors if the argument put into the function is inadvertently different than
the actual assigned object.

Furthermore, the clarity of the code is compromised with the addition of these parameters. Example:

A: x <= to_unsigned(1)
B: x <= to_unsigned(1, 8);

In 'A', it is abundantly clear that all that is being done is to convert the integer '1' into an unsigned signal 'x'. In 'B', it is not so obvious because of the '8' parameter. Granted most developers already 'know' that 'B' is converting the integer '1' into an 8 bit unsigned, but as can be seen by the recent development of the 'fixed_pkg' set of functions there can be a lot more functions that could make use of the knowledge of attributes of the intended target and using those functions start getting ugly really quickly because of the need to pass in attributes of the target.

Requirement

A function already has some knowledge of the target in that it knows the type of the target. What is missing for array types is simply knowledge of the array attributes that can be important as mentioned previously. One solution is to provide a new keyword (I'll call it 'constrained') that is part of the function declaration and another keyword (I'll call it 'that') that a function can access to obtain the array attributes. A given function can be overloaded either containing the 'constrained' keyword or not; the compiler would determine which function to call based on whether the target has known size (call the 'constrained' version) or if it does not (call the typical version of the function). As an example, 'to_unsigned' could be defined as follows:

*** Note: The new keyword 'constrained' might not be needed after all if one makes the assumption that any code existing today must have at least one extra function input argument that defines the output attributes that are needed. That parameter(s) would not be needed since the information would be available from that'length, that'left, that'right, etc. See further discussion later on this page from this date -- KevinJennings - 2014-12-19


function to_unsigned(x: natural) return constrained unsigned is
begin
return(to_unsigned(x, that'length));
end function to_unsigned;

This would allow for the following usage:

x <= to_unsigned(1);

as well as code clarity improvements to how fixed_pkg and possibly other packages get used in real designs.

The primary intended usage of this proposal is to allow access to static attributes of the target (i.e. x'left, x'right, x'range, etc.), not the dynamic signal attributes (x.'event, x'transaction, x'stable, etc.). Access to the dynamic attributes would imply that the target of the function is a function of itself. All of the currently defined VHDL array attributes are static, therefore the new form of function defined here would not make a target signal that is the output of this type of function be a function of itself.

This idea can be extended further to include scalar objects but would require adoption of another (as yet unwritten) proposal. For scalars, it is valid today to refer to attributes such as 'left, 'right, etc. of a type, but not for an object of that type. Assuming that one could get attributes of a scalar object, then object'left, object'right would then return the left and right bounds of that scalar (i.e. signal xyz: integer range 2 to 5 --> xyz'left = 2; xyz'right = 5). All of the other usual attributes would also be available, not just 'left and 'right. On the assumption that this new proposal happens, then this proposal would automatically inherit the ability to access scalar attributes as well. This would allow the function to know the range of the output object. It has been suggested in separate e-mails that this would be a benefit. -- KevinJennings - 2014-04-23

Implementation details

Code Examples

Use Cases

See examples posted in 'Current Situation' and 'Requirement'

Arguments FOR

- Clearer design intent since assignments are not cluttered with function parameters that are only input in order for the function to obtain the value of the attribute (in this case 'length).

- Less chance for latent design error: x <= to_unsigned(2, y'length); rather than the intended x <= to_unsigned(2, x'length); is an example.

Questions/Comments

OK, I am officially changing my mind to support this proposal.

My question is why do we need the new suggested keyword 'constrained'?

On an input array parameter, we access its attributes such as "arg'length" whether it is a constrained or unconstrained array.

Do we need to know if the output type is constrained when accessing its attributes (return'length or that'length)?

-- Brent Hayhoe -2013-04-11

Reply

One difference is that inputs have actuals when the function is called. The output of a function may be the input to another function -- in general to any expression. Here is a silly but valid example. Say both "resize" and "to_unsigned" are defined using this proposal. Then how big is the intermediate result in

resize(to_unsigned(15), 3)

? This example is silly, but the fixed-point operations change vector lengths for most arithmetic operations. So maybe a better example would be

a + to_sfixed(-1.562)

. What should be the index range for the to_sfixed output?

We don't have an actual for the output, so we can't in general try to access attributes like 'length.

-- RyanHinton - 2013-04-12

As already mentioned in the 'Requirements' section, if the target has a known size the compiler would call the 'constrained' version of the function (if it exists), otherwise it would call the typical version of the function. For the examples given, the function target size is not known so the 'constrained' form would not be called. -- KevinJennings - 2014-04-23

Arguments AGAINST

Whilst I like the idea of reducing complexity, I am against this proposal.

Taking the 'to_unsigned' example, if the length parameter was left to be implicitly specified, the function would be incapable of being elaborated until elaboration of the code for each instantiation of the function. I can see lots of compiler overheads in this approach and almost certainly ambiguities arising.

The problem is solved as stated above:

  x <= to_unsigned(2, x'length);

i.e. using good structured design practise. The 'latent design errors' are, I'm afraid, just user implementation design bugs.

-- Brent Hayhoe -2013-04-04

Rebuttal

- "the function would be incapable of being elaborated until elaboration of the code for each instantiation of the function". This argument makes no sense. Using the 'to_unsigned' example again, with today's language definition, if there is a mismatch between the size actually returned by the to_unsigned function and the vector it is being assigned to it results in a run-time error. How is that 'better' than what I have suggested where there would be no error of any sort? There is nothing implicit except perhaps for use of the new keyword in the function...but being part of the LRM it would no longer be considered implicit. The function now has explicit access to the attributes of the object to which it is going to be assigned.

- "I can see lots of compiler overheads in this approach and almost certainly ambiguities arising." Can you suggest any actual examples of such 'overheads' or 'ambiguities'?

- "The problem is solved as stated above:"...No, what I provided was a solution to prevent the problem. Using today's syntax is the problem, not the solution.

- "i.e. using good structured design practise" In what way would having an object be a function of itself to be considered 'good structured design practise (sic)"? In fact, assigning something with the output of a function that uses that same something as an input parameter is highly questionable.

- "The 'latent design errors' are, I'm afraid, just user implementation design bugs" That's a decent working definition of a 'latent design error'...which is something to avoid. My proposal would avoid the potential bug of inadvertantly having the wrong thing specified as an input parameter in the first place, preventing the bug.

-- KevinJennings - 2013-04-04

Reply

So where we have a conversion to integer as in:

    my_integer <= to_integer(my_vector);

defined by the function:

    function to_integer (arg : unsigned) return integer;

which uses arg'length at run-time to determine the input vector size;

then the conversion to unsigned:

    my_vector <= to_unsigned(my_integer);

defined by a new function:

    function to_unsigned (arg : integer) return unsigned;

would use some sort of syntax like return'length to determine the output vector size at run-time.

OK this could be good. We need some compiler experts input to say why this can or cannot be done.

-- Brent Hayhoe -2013-04-05

-- DanielKho - 2014-12-18:

"then the conversion to unsigned:

    my_vector <= to_unsigned(my_integer);

defined by a new function:

    function to_unsigned (arg : integer) return unsigned;

would use some sort of syntax like return'length to determine the output vector size at run-time."

I'm not sure if I see the logic here. What is the size of return'length? I would think the function to_unsigned knows the size of its argument, which in this case is integer, and will return a 32-bit unsigned number. Is this the intent of the designer? Probably not. -- DanielKho - 2014-12-18

Okay, I think I see the point of this proposal. The return values of functions get automatically resized according to the size of the target, and not according to the size of the input argument(s). Am I right? So to_integer will resize the output to the size of my_integer, and to_unsigned will resize according to the size of my_vector? If so, I'm supporting this. -- DanielKho - 2014-12-18

KJ reply: Yes, to_unsigned resizes according the target size. Rather than repeating, please see the definition of the example to_unsigned function body in the requirement's section. -- KevinJennings - 2014-12-19

Another comment

I am against this proposal on the grounds of implementation complexity. The expression intermediate sizes must be determined, if possible, and passed to the function being called. This requires a change to the calling stack if done at run time or to the instance mechanism if done at elaboration time.

In simple assignments in procedural code, the function can often be replaced by a procedure, allowing access to the target array size:

  proc_to_unsigned(x, 2);

This approach requires no change to the language but does not help initialization unfortunately.

-- PeterFlake - 2014-10-02

Note: Peter, what you refer to as 'This approach', was the poster posting what is currently defined in the language. The proposal defined on this page would help initialization (among other things) and would improve the current situation so it is not clear why you're a non-supporter of the proposal if your comment here is the only reason. -- KevinJennings - 2014-10-11

Defining constants

I understand the desire here. Several times recently I have wanted to know the size of the output in order to size the result of a function. However, I constantly use function return values to set the dimensions on unconstrained arrays. I want to keep this feature. But I don't like using overloading to determine whether a function can access the output size.

So I would like for this problem to be solved, but I don't like the solution yet. I would like it if

  • I can still use the return subtype to constrain constant declarations, and
  • I don't have to use overloading to handle both known and unknown cases.

-- Ryan Hinton -2013-04-10

Reply

Overloading isn't what would determine whether a function can access the output size. The new keyword 'constrained' in the function declaration is the trigger.

function abc(...) return constrained unsigned; -- Function body would be allowed access to attributes of the return type

function xyz(...) return unsigned; -- Function body would not be allowed access to attributes of the return type

Any declaration without the keyword (i.e. all code that has ever been written to date) would work just the same as it does today which addresses your first bullet point. There is no requirement in my proposal to use overloading to access the target's attributes so your second bullet point is also covered. On the other hand, I wouldn't want to see an implementation of the standard be such that overloading is prohibited. The reason for that requirement/request is that if function overloading is prohibited then you now would have something like this...

b <= to_unsigned_xxx(1); -- Where '_xxx' is something to distinguish the function from the well loved 'to_unsigned'.

There is no purpose being served by making the user remember which form of the function needs to be used, that's what a compiler can keep track of and is one of the benefits of using overloading. I would expect then that current packages that are part of the standard would be updated to include the additional 'constrained' forms. That would allow users of these packages to immediately take advantage of the new syntax if they choose. Whether users also choose to write their own functions with the new syntax in mind or not is their choice.

-- KevinJennings - 2013-04-10

Reply2

My second bullet is intended to say I don't want to write the function twice (i.e. use overloading) based on whether or not I know the output sizing. Thinking about it more, I would use the output size for constants and variables in the declarative part. Without overloading, I would need ternary expressions or messy functions to determine my variable ranges. What a mess. Also, writing two functions wouldn't be too bad since the not-constrained one can simply create a variable of the desired length and assign it from the constrained version. So I withdraw this objection.

But I add a new objection. How is the compiler supposed to know when to use the constrained overload?

-- RyanHinton - 2013-04-12

Reply3

If the target of the function is of known size, then the constrained version of the function would be called. If the target is of unknown size then the type of function that is used in today's world would be called. Some examples where the target of the function might not be known upfront are:

- Nested functions: Compiler might not be able to determine the size to use for the intermediate outputs of the innermost function(s) so the 'constrained' version would not be called

- The function itself determines the size of the output (i.e. constant xyz: std_ulogic_vector := My_function(...)). In this case, the left hand side is defined in the source code as an unconstrained vector.

In these cases, the compiler would not call the constrained version of the function since the target doesn't meet the requirements. It's really no different than how the compiler knows which function to select based on the type of the target.

Both of these examples are perfectly legit code today, and they would remain so tomorrow. The constrained version of a function simply gives the compiler one more function template to match which it would use if the target of that function happens to be a vector of known size.

-- KevinJennings - 2013-04-12

Does this simplify the situation?

On one respect, this makes the syntax for some assignments, initializations, and port maps simpler. Hence, the following works:

  signal Y : unsigned(7 downto 0) := to_unsigned(5) ; 
  signal B_int : integer ; 
  ...
  Y <= to_unsigned(7) ;
  ...
  U_comp : comp
  port map (
    Z =>  to_unsigned(B_int), 
    ...
  ) ; 

However, as soon as multiple operators are used, this simplification no longer applies. Hence, the following are illegal uses of this same conversion function:

  signal Y : unsigned(7 downto 0) := to_unsigned(5) and X"FF" ; 
  ...
  Y <= to_unsigned(7) + 5 ; 
  ...

Hence, this proposal only applies to a limited set of use cases. It is the inconsistency of its usage that I find problematic, and hence, I cannot support it.

Chief Complaint?

Based on the examples, this proposal's chief complaint seems to be assigning integer literals to array values. Kevin, is this right?

Unfortunately, the proposed implementation only works in limited cases, specifically at the outer most point of an expression (direct assignment, or port map).

I am infavor of addressing the chief complaint as I have summarized above. If the chief complaint is more general than what I have summarized above, then we need use models for those situations.

Reply to 'Chief Complaint'

- No you are not correct about "...assigning integer literals to array values", please review 'Current Situation' which has an example using the fixed point package. Users of the fixed point package would gain immediate relief if this proposal is adopted...unless you really enjoy the syntax of typing in xyz'left, xyz'right into all those functions. Also review the last paragraph of 'Requirement' which details potential future benefits to be gained and a cleaner solution than the proposal NewPredefinedAttributeActual.

- Everything only works within limited cases.

- It is not clear what you are referring to with your statement 'I am in favor of addressing the chief complaint as I have summarized above'. All I see above is an example where the proposal would not apply that you wish it would. The limits of where this proposal would be useful was detailed in the original proposal and all you've provided is an example of something to which it would not apply (but you would like it to apply). I realize you oppose this proposal because the scope is not as large as you would like, but in what way have you contributed to improving it that would allow you to say 'I am in favor of addressing the chief complaint'?? The proposal as I wrote it, has the following benefits (all of which were detailed upfront):

1. Cleans up some questionable looking code (i.e. the meaning of '8' in to_unsigned(1,8)...and several use cases such as 'resize' when using fixed_pkg where one must pass in the 'left and 'right attributes of the target).

2. Prevents latent design errors that would otherwise not surface until runtime (i.e. y <= to_unsigned(1, x'length);

3. Has potential for helping to properly define subtype ranges

What I'm not seeing in any of the discussion on here is dialog along the lines of:

- Does the proposal, as written, address what it says it does?

- Are there better ways to address the problem than this proposal?

- Is the scope clearly defined?

- Are there holes in the proposal?

You're free to oppose because the proposal doesn't cover as much as you'd like it to, or because you've outlined how it would be way to complicated to implement or to oppose it for no stated reason if you'd like. However, items #1, 2 and 3 listed above have been addressed by my proposal. That much (so far) is undisputed so this proposal would be a benefit to the language. Opposing it because you would like it to do something that it is specifically not intended to is like opposing the screwdriver because you wanted a hammer. You can do that, but there is no rational discussion that will follow. All I can do in that situation is to once again describe what the screwdriver does and what it does not.

--Main.KevinJennings - 2014-10-12

RE: Reply to 'Chief Complaint' So if I expand the chief complaint to, "assigning and mapping (both port and parameter) integer literals to array values (such as signed, unsigned, ufixed, sfixed, float) and assigning real literals to array values (such as ufixed, sfixed, and float)", does that cover it? Note that the conversion will only work at the outermost level of an expression (at an assignment or mapping level), and never when mixed within an expression.

Are there any other use cases that you are concerned about? I see some mention of accessing scalar range information, but I do not see any proposed usages of this. Before making any alternative (and potentially subsuming) suggestions, I want to make sure I am addressing the right concern. I would not want to start another proposal like FunctionsKnowSubtype that goes in a direction that does not address your chief complaint. I think you will like what I have in mind, so please humor me so I can get it right.

General Comments

From an implementor point of view, I don't see any issues. Calls to such function would be allowed at the same place where '(others => xxx)' aggregate are allowed. These places are already defined by VHDL, and the restrictions to use such function are already defined for the aggregate. -- TristanGingold - 2014-10-21

Could we do without the 'constrained' keyword and let the compiler decide how to automatically size the output? If the overloaded function uses 'that', then the compiler knows how to resize the output based on the size of the target. If none of the overloaded functions use 'that', then the compiler defaults to use the unconstrained version. Means, the compiler implicitly resizes without us having to write overloaded functions with the extra keyword 'constrained'? -- DanielKho - 2014-12-18

KJ reply: The keyword 'constrained' (or some other keyword) might still be needed. When deciding which function to use, the compiler only looks at the function declaration and matches argument types (and with this proposal, I'm suggesting an additional keyword 'constrained' which is part of the declaration). The compiler does not examine the function body where 'that' would be used. Although the 'to_unsigned' example is a function where without this proposal (i.e. today's LRM) an additional parameter must be input to the function to define the size, therefore it would have a different declaration then one that does use this proposal (i.e. the 'length' parameter would not be needed as a function parameter since it would be available as "that'length"), I'm not totally convinced that this would always be the case (discussed a bit more at the end of this reply).

However, if we assume that there would be no such collisions, then the 'constrained' keyword would not be needed at all since the assumption would imply that functions based on today's LRM would always have at least one extra input parameter that gets used to define the output length. Again, using to_unsigned as an example, in addition to today's world we have:

function to_unsigned(Arg, Size : natural) return unsigned;

With this proposal, there would simply be another overloaded to_unsigned which would use that'length to retrieve the required output size.

function to_unsigned(Arg : natural) return unsigned;

Since the new function has a different number of arguments then the original they can peacefully coexist, there would be no ambiguity to the compiler. It would also mean that the compiler doesn't need to do any analysis different from today to see which function should be used. The only change to today's compilers would be in interpreting the function body where it would need to know about the new keyword 'that'.

The potential problem with making the assumption that there would always be at least one extra input parameter is that it is conceivable that the function could receive as input a record where one of the elements of that record defines the output size. In that case, the function that would use 'that' would have the same function declaration as the one that does not. But maybe that case is not important since there would be no need to write a new form of the function that uses 'that' since the input record would already have defined the output size required.

Lastly, although this part of the discussion has only been about the 'length attribute, it would apply equally well to the other attributes as well. For example, the fixed point package has functions that pass in 'left and 'right. Again with access to that'left and that'right those input parameters would not be needed so the 'new' form of the function would have a different function declaration than today's so it would not be ambiguous.

-- KevinJennings - 2014-12-19

Current Status (June 2, 2016)

May 19 2016 meeting (http://www.eda-twiki.org/cgi-bin/view.cgi/P1076/2016_MeetingMay19) decided to go ahead with this at least as far as writing up proposed LRM changes. The only change was that the function declaration would include the name that the function body would refer to when accessing attributes of the target as shown highlighted in red below.

function to_unsigned(x : integer) return xyz : unsigned;

The body of the function with such a declaration would then access the target attributes by referring to xyz'left, xyz'right, xyz'length, as needed. The proposed list of allowable attributes are:

  • The elements 'BASE, 'LEFT, 'RIGHT, 'HIGH, 'LOW, 'ASCENDING, 'SUBTYPE defined in 16.2.2 'Predefined attributes of types and objects'.
  • All attributes defined in 16.2.3 'Predefined attributes of arrays and scalars'
LRM section 16.2.3 currently defines predefined attributes of arrays. This proposal would also propose to predefine the same set of attributes for scalars. This would allow accessing the range of the target. For example, given signal abc: integer range 2 to 5; then a function could determine the range of the target (i.e. 2 to 5) if so needed. (Note: Discuss this at next meeting as well since this is going a bit beyond what was discussed and agreed on May 19).

Although the name 'FunctionKnowsVectorSize' should probably be maintained so as not to break links, a perhaps better name for this proposal is 'FunctionKnowsTargetAttributes'

Proposed LRM changes to support proposal are here: LRM_Mods_Function_Knows_Target_Attributes.docx:

-- KevinJennings - 2016-06-02

Non-supporters

-- JimLewis - 2014-06-22

-- PeterFlake - 2014-10-02

-- DavidKoontz - 2014-10-11

-- ErnstChristen - 2015-01-27

Supporters

Add your signature here to indicate your support for the proposal

-- KevinJennings - 2013-04-04

-- Brent Hayhoe -2013-04-11

Topic revision: r34 - 2016-06-08 - 12:48:19 - KevinJennings
 
Copyright © 2008-2019 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback