Implicit Numeric Conversions
Proposal Editing Information
- Who Updates: JimLewis, <Add YourName>, ...
- Date Proposed: 2014-10-14
- Date Last Updated: 2014-10-14
- Priority:
- Complexity:
- Focus: RTL and Testbench (especially randomization and functional coverage collection)
- Proposal Maturity: Brainstorming
Requirement Summary
Implicitly convert from integer to unsigned, signed, from real to ufixed, sfixed, and float, and vice-versa. Obviously with some constraints - specifically, like "??" defined for conditional expressions, the implicit conversion will only occur at the outermost level of an expression (assignment, case choice (?only ok with locally static select expression?), port map (ambiguous if port not constrained - for RTL generally the are constrained), ?parameter map (likely procedures only since functions are never at the outmost level of an expression) (ambiguous if input unconstrained - common)?, indexing, slicing).
Implicit conversions in general can lead to incorrect code. However, here, types integer, unsigned, and signed fundamentally hold an integer. Likewise, types real, ufixed, sfixed, and float fundamentally hold a real number.
Due to the collision of strong typing and operator overloading, we have evolved to a use model that seemingly has inconsistencies with respect to usage of literals. This proposal will allow integers and real parameters to be used in either assignments or expressions.
Referenced Issues
FunctionKnowsVectorSize, ISAC IR2132, and Bugzilla #240 identify the concern over 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)"
OverloadAssignment, ISAC IR2130, and
Bugzilla 239 suggest overloading the assignment operation without really proposing a use case, so it is not clear exactly the issues it is trying to address.
Current Issues
For types, unsigned, signed, and std_logic_vector, string literals work well, however they lack dynamic sizing. Hence, if I parameterize the size of Y_unsigned, then I must also parameterize the literal sizing. While 1076-2008 allows for sizing of bit string literals, this does not support dynamic sizing.
signal Y : unsigned(Port_A'range) := to_unsigned(15, Port_A'length) ;
signal X : unsigned(Port_A'range) := resize("1111", Port_A'length) ;
--! signal Z : unsigned(Port_A'range) := Port_A'lengthD"15" ; -- illegal
For types, unsigned, signed, and std_logic_vector (when numeric), usage of integer literals is allowed in numeric expressions, however, not in assignments (or mapping):
signal A, B, Y : unsigned(7 downto 0) ;
. . .
--! B <= 15 ; -- illegal
B <= to_unsigned(15, B'length) ; -- legal
Y <= A + 15 ; -- legal
For types ufixed, sfixed, and float, there is no one literal that works in all situations without extra help (conversions or qualification).
subtype ufixed4x2 is ufixed(3 downto -2) ;
signal A, B, Y : ufixed4x2 ;
. . .
A <= "011101" ; -- 7.25, legal
--! Y <= A + "011101" ; -- illegal
Y <= A + ufixed4x2'("011101") ;
--! B <= 7.25 ; -- illegal
B <= to_ufixed(7.25, B) ;
Y <= B + 7.25 ; -- legal
Indexing requires an integer. However, any unsigned or signed value, when interpreted as an integer could be acceptable.
Y <= ROM(to_integer(A_unsigned) ) ;
Proposal
Passing Subtype information to a function
This section is similar to
FunctionKnowsVectorSize, see later section that compares differences.
When a subprogram contains the keyword "subtype", allow it to access subtype information (mainly array range, length, left, right; ?scalar left, right?) use the keyword "return" as a prefix. This subprogram may only be called where the subtype information is available (ie: for assignments and mappings (port or parameter) with a locally static type - in another post,
TristanGingold suggests that , "Calls to such function would be allowed at the same place where '(others => xxx)' aggregate are allowed").
function resize(ARG : unsigned) return subtype unsigned is
begin
return(resize(ARG, return'length));
end function resize;
Normal subprogram overloading rules still apply. Hence, if two "+" operators are visible, one with the "subtype" keyword and the other does not, an expression that uses "+" with the corresponding parameters is ambiguous. Hence, using both of the following subprograms would result in an ambiguous expression.
function "+"(l, r: unsigned) return subtype unsigned is
variable result : unsigned (return'range) ;
begin
. . .
end function "+";
function "+"(l, r: unsigned) return unsigned is
variable result : unsigned (return'range) ;
begin
. . .
end function "+";
While it would be ok to create a package that only used the "subtype" keyword, however, it would also limit the use of the operators at the top level of an expression, and hence, only one math operator per assignment.
This part of the proposal still leaves us plagued with conversions between like types.
Implicitly calling an operator
Add an operator that can be called at the outermost level of an expression to convert between types. It would be convenient if the operator could be "??" as this would be an expansion of its usage in VHDL-2008 conditional expressions. Outermost level would imply that this only works with procedures
function "??"(l : integer) return subtype unsigned is
begin
return(to_unsigned(l , return'length));
end function "??";
Use Model
Simplify dyanmic sizing of literal values:
signal Y : unsigned(Port_A'range) :=15 ;
Consistent usage of integers within numeric_std:
signal A, B, Y : unsigned(7 downto 0) ;
. . .
B <= 15 ; -- legal
Y <= A + 15 ; -- legal
Consistent usage of real and a literal that works for everything in
FixedPkg (fixedGenericPkg):
signal A, Y : ufixed(3 downto -2) ;
. . .
A <= 7.25 ; -- legal
Y <= A + 7.25 ; -- legal
Indexing requires an integer. However, any unsigned or signed value, when interpreted as an integer could be acceptable.
Y <= ROM(A_unsigned) ;
Conversions work in port and parameter maps. Limitations apply: the type of the port or parameter must be locally static (meaning, if it is an array type, it has a range constraint). Currently converting at function parameter maps is disallowed.
AnEntity_1 : use entity work.AnEntity port map (in_unsigned_1 => 1, ....) ;
B_procedure (in_unsigned_1 => 1, ...) ;
Conversions work in case choices (provided the case select expression is locally static (meaning, if it is an array type, it has a range constraint):
CombProc : process(all)
begin
case my_unsigned is
when 0 => Y <= A ;
when 1 to 2 => Y <= B ;
when 3 => Y <= C ;
when others => Y <= (others 'X') ;
end case ;
We have a number of proposals that could also benefit from having a conversion of this sort.
Cautions / Warnings
While "??" can be a great tool to simplify conversions, just like function overloading, it could facilitate some bad things as well.
Conversions between types that have similar literals may lead to code correctness issues
function "??"(A : unsigned) return subtype signed is
begin
return(signed('0' & A));
end function "??";
With this one can write the following code. Lets assume that we can solve any potential language issues with this. Conversions like this may have an undesirable impact on code correctness. For example, subtract an unsigned from an unsigned and assign it to a signed value (see code below). While the types are correct, the the result of the subtraction is unsigned (unsigned - unsigned results in type unsigned), and hence, when it is implicitly converted to signed, the value will never be negative, even when B is larger than A.
signal Y : signed(8 downto 0) ;
signal A, B : unsigned(7 downto 0);
...
Y <= A - B ;
Conversions between same type
If conversions between same type are allowed, which these may not be allowed due to the various restrictions we have to put on the operation to keep existing code working (for example, "??" may be restricted to be used only when an existing overload does not satisify the equation):
function "??"(A : unsigned) return subtype unsigned is
begin
return(resize(ARG, return'length));
end function "??";
This would make all of the following legal assignments:
signal A8, B8 : unsigned(7 downto 0);
signal Y7 : unsigned(6 downto 0) ;
signal Y8 : unsigned(7 downto 0) ;
signal Y9 : unsigned(8 downto 0) ;
...
Y8 <= A8 + B8 ;
Y7 <= A8 + B8 ;
Y9 <= A8 + B8 ;
My opinion is that at a minimum we should be using resize in this situation rather than automatic conversions. In addition, this proposal simplifies the use of resize. We resize, the assignments would be coded as:
Y8 <= A8 + B8 ;
Y7 <= resize(A8 + B8) ;
Y9 <= resize(A8 + B8) ;
OTOH, even resize may be too general and I would be in favor of adding some functions that carry and enforce the intent of the code. Where downsize will either reduce the size or leave it the same size and upsize will either increase the size or leave it the same size. This would simplify catching issues with design parameterization gone astray.
Y8 <= A8 + B8 ;
Y7 <= downsize(A8 + B8) ;
Y9 <= upsize(A8 + B8) ;
Other Concerns
There may be additional issues with this due to ambiguity of calls. If the calls are restricted to the outermost level (meaning disallow implicit conversions on operator and function inputs), then we are probably ok with respect to ambiguity. This issue would have to be revisited if we allow conversions on function inputs (meaning remove the restriction of calling from the outermost level.
The section "Passing Subtype Information to a function" contains many similarities to
FunctionKnowsVectorSize. Here I will point out the differences and rationale.
Keyword "subtype" vs keyword "constrained". Using keyword subtype reuses an existing language keyword and the information that is accessed is subtype information - while
FunctionKnowsvectorSize primarily accesses subtype attributes of arrays it also postulates accessing subtype attributes of scalars (ranges).
Keyword "return" vs. keyword "that". Either is equally acceptable. Using keyword "return" reuses an existing language keyword and avoids potential backward compatible usage of "that".
This proposal uses current language rules for subprogram overloading. If two functions both have the same parameter names, parameter types, and result type, then calling the subprogram is ambiguous and an error independent as to whether the subprogram contains the "subtype" keyword or not.
FunctionKnowsVectorSize also uses the "subtype/constrained" keyword to disambiguate this situation. Using this feature to resize a "+" operation would remove result sizing protections currently provided by the language. A safe use model for this extra capability has not been demonstrated (with code example or other) in
FunctionKnowsVectorSize so it was excluded.
Proposal
FunctionKnowsVectorSize adds one parameter function calls to_unsigned, to_signed, to_ufixed, to_sfixed, and to_float functions, this proposal does not. While using the explicitly defined to_unsigned, ... functions will save some typing in very specific situations, they do not work in all situations and as a result, for many, they will result in more confusion rather than less.
This proposal adds implicit conversions between types integer and unsigned (or signed), real and ufixed (or sfixed or float). This is intended to simplify conversions (similar to the intent of a one parameter to_unsigned function), without creating an asymmetric usage model. For math operations, overloading of the form, "+" [signed, integer result signed] is already supported. Hence, mixing integers and signed is already permitted. Extending assignments to convert from integer to signed, further affirms the similarity between these types.
Questions
Currently I have disallowed conversions in function parameter maps? This seems heavily problematic. It would require the parameter type to be locally static - it may simplify things some as it would remove the check from outermost level of an expression. However, the overloaded math operators, such as "+" which allow both array with array and array with integer parameters seem to be a huge problem. If "??" were used to converted a string literal to an integer, then the expression would be ambiguous. As a result, it would only be allowed to call "??" to convert a parameter when no other feasible overloading exists. Is this reasonable? It also seems the pain level may exceed the usefullness - keep in mind it will not work for unconstrained parameters and so we cannot simplify the math packages by removing the integer parameters. --
JimLewis - 2014-10-14
General Comments
For passing subtype information to a function, I like the reuse of 'subtype'. May I propose to use 'return' as attribute prefix for getting constraints of the result ? --
TristanGingold - 2014-10-15
I like it. Thanks --
JimLewis - 2014-10-16
KJ Comments and Responses
With all of the comments in the middle of the proposal, I was having problems following what was the proposal and what was not, so I have collected all of KJ's comments to here and provided responses to them.
-- KJ Comment: Because you're putting this into your 'Proposal' section, this seems to be saying that
ImplicitConversionNumeric is taking
FunctionKnowsVectorSize as a base proposal to begin. But you are currently listed as a Non-Supporter of that proposal which then implies that you must be a non-supporter of your own
ImplicitConversionNumeric proposal. Interesting.
--
JimLewis -- By itself, I do not support
FunctionKnowsVectorSize because I do not think that a single parameter to_unsigned, ... makes a confusing situation any less confusing - see my examples in that proposal. --
JimLewis
-- KJ Comment: Actually, you did not suggest to use the keyword subtype in
FunctionKnowsVectorSize, you appear to be making the suggestion here in a completely different proposal for some reason. I suggest moving your suggestion to
FunctionKnowsVectorSize since it does not appear to apply to this proposal. Also, I do not agree that in
FunctionKnowsVectorSize that the keyword 'constrained' is about accessing subtype information. Per that proposal, "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". That proposal is clear that it applies to vectors and whether they are constrained or not. That is not the same thing as a subtype which can be scalars or vectors. --Main.KevinJennings - 2014-10-20
--
JimLewis - We have talked about subtype alot and we still disagree. Hence, I put this forward separately from your proposal because it is up to the VHDL community to decide syntax - not me and not you. I reference your proposal since you put together the original idea and I want to make sure to credit you for it. --
JimLewis -
-- KJ Comment: Again, if you have a suggestion for
FunctionKnowsVectorSize, then I suggest you should make it in that proposal, not here where it is not relevant. Whether or not the 'return' keyword can be reused both as something to do to exit a function and as a reference to the thing that is ultimately being assigned to should be made by compiler creators (Tristan being one). I don't have a particular objection to using 'return', and I don't thing my proposal of 'that' is necessarily great either, but I'm guessing (and could be wrong) that a new keyword would be cleaner when it comes time to implement the suggestion in actual code. Also the intuitiveness of 'return' is not clear to me. The thing that is being referenced is not the thing that is being returned by the function, it is the thing to which the function will be assigned. Those are different things. --Main.KevinJennings - 2014-10-14
--
JimLewis - see my previous comment --
JimLewis
-- KJ Comment:
FunctionKnowsVectorSize is clear, there is no ambiguity as you are suggesting in this proposal. See my earlier comment. Put simply, if the target of the function has a known size then the 'constrained' (or 'subtype' as you call it) function would be used. --
KevinJennings - 2014-10-14
--
JimLewis -- I see having identical overloading, where one has constrained/subtype syntax and the other does not as problematic. --
JimLewis
-- KJ Comment:
FunctionKnowsVectorSize clearly defines the scope of the problem that it is addressing and proposes a solution for that problem. This proposal of yours so far has not clearly defined a scope and solution but you refer to it as a 'plague' for some reason.
--
JimLewis -- see my comments on strong typing --
JimLewis
-- KJ Comment: Wow, you propose to break strong typing, one of the most valuable features of VHDL. --
KevinJennings - 2014-10-20
-- KJ Comment: Agreed, once strong typing goes out the window, much now becomes 'legal'.
--
JimLewis - I agree. Strong typing is one of the most valuable features of VHDL. However, we have already partially disabled it and acknowledged that type natural and unsigned, and integers and signed are really the same thing when we created overloading with the signature, "+" [unsigned, natural, return unsigned]. It is confusing for many that they can use integers in expressions, however, they cannot assign integers to unsigned. This proposal just takes the next logical step - natural and unsigned are different representations of the same value - hence, one should be able to fully intermix the types. Strong typing is in place to help enhance the correctness of code - this proposal does nothing to break that intent. --
JimLewis
-- KJ Comment: Now 'unsigned' and 'signed' are special in that they will need to be implicitly interpreted as integers and will receive special treatment. To bad for 'ufixed' and 'sfixed', they missed the boat and will not get this special treatment (i.e. ufixed(3 downto 0)).
--
JimLewis -- Ironically the example that originally followed this comment showed ufixed being used with a real number, so it did not miss the boat. I feel comfortable assigning integer literals to unsigned and signed and also real literals to ufixed, sfixed, and float. Although I do not mention it, I would be agreeable to assigning integer literals to ufixed, sfixed, and float - however, I do not in any way consider this one essential --
JimLewis
-- KJ Comment: - I'm not a fan of breaking strong typing and allowing things like 'my_unsigned <= 7;' in the source code. I'm not a fan of implict user written code either.
--
JimLewis -- Answered in previous comments --
JimLewis
-- KJ Comment: - A cursory side-by-side comparison of
ImplicitConversionNumeric and
FunctionKnowsVectorSize seems to me to have a list of 'concerns' that you already have for your own
ImplicitConversionNumeric proposal, none of which apply to
FunctionKnowsVectorSize. In fact,
within the scope defined in FunctionKnowsVectorSize, the solution in that proposal has had no concerns raised and has been out there for quite a while. There are likely other 'concerns' that will surface for
ImplicitConversionNumeric as well upon closer review.
--
JimLewis -- What I am looking for is a proposal that makes the language more regular and reduces the number of exceptions. Adding a one parameter to_unsigned as
FunctionKnowsVectorSize suggests creates more exceptsions. Creating an implicit integer conversions harmonizes with existing overloading for types unsigned, signed, and std_logic_vector (if added to numeric_std_unsigned) and creating implicit real conversions harmonizes with existing overloading for types ufixed, sfixed, and float. Without this proposal, there is no literal that works with both assignment and "+" for types ufixed, sfixed, and float - from a language understanding standpoint - this is not good. --
JimLewis
-- KJ Comment: - You seem to view a new keyword as a major impediment. What is the basis for that and is it real? While I understand that one would not want to add a keyword if there is another solution, that does not imply that adding a keyword is any drawback if it is required to implement a change.
--
JimLewis -- one of our prime directives is to mimimize the probability of breaking old code - however, I do not think we should do this at the expense of using an appropriate keyword. I think subtype is the best keyword, you don't - that is ok, again it is not up for us to decide it is up to the working group to decide. --
JimLewis
-- KJ Comment: - What exactly are the benefits of
ImplicitConversionNumeric compared to
FunctionKnowsVectorSize? While it seems like you think that something like 'my_unsigned <= 7' can be viewed as a benefit, not all would agree with that position. That is just an example, not the exhaustive list.
--
JimLewis -- answered above --
JimLewis
-- KJ Comment: - The general form of this proposal is very muddled. Rather than referring to a different proposal and not being clear about where that one ends and yours begins, it would be much better for you to state your proposal clearly without copying stuff from
FunctionKnowsVectorSize. Then I would suggest a simple side by side comparison of the two proposals instead. The apples to apples part of that comparison would be comparing only between things of the same problem scope. You can then add an apples to oranges part that compares benefits and concerns of the more widely scoped
ImplicitConversionNumeric. It could very well be that only addressing problems within the scope of the apples to apples part is actually worth doing and that things that fall outside that scope are too problematic or maybe not that generally important.
--
JimLewis -- This proposal is intended to be complete on its own. I was hoping you would like it. I reference
FunctionKnowsVectorSize to both credit you and to point out differences between them. I have moved differences between the proposals to a separate section as you suggested. --
JimLewis
--
DanielKho - 2014-12-18 -- On the topic about strong typing, there is still much debate between those who prefer simplicity over verbosity. Yes, there are already many "convenience" functions in VHDL that simplify things like type conversions, and blurs the idea of strong typing. My view is that however many convenience functions we implement into VHDL, we may not be able to break the strong typing features of the language, as internally, it is still strongly typed. Like Jim mentioned, the underlying concept of integer is similar to unsigned, signed, natural, positive, and it just makes sense that conversions between these types be made easy and implicit. Still, there may be many who prefer to make explicit conversions - why not implement these convenience functions as part of a different package (other than numeric_std), so people can choose whether or not to use it? For those who like the new and easy way of doing things, they could include another package, while others can use the usual numeric_std.
<Brent Hayhoe> - I think we are confusing simplicity (via implicit function calls) with breaking strong typing. The two are not mutually exclusive. Specifically 'my_unsigned <= 7;' should not necessarily break strong typing.
In a similarly manner that 'if (A = B) and my_std_logic then' is valid by the implicit calling of the '??' (std_ulogic to boolean) condition operator, then a similar (unsigned to integer) condition operator could also be defined.
The operator creates simplicity because of the implicit calling of the function. It doesn't break strong typing because you can trace back the type conversions through the function.
</Brent Hayhoe> - 2014-12-18
Non-Supporters
--
KevinJennings - 2014-10-20
Supporters
Add your signature here to indicate your support for the proposal
--
JimLewis