Physical Type Ranges

Proposal Details

  • Who Updates: KevinThibedeau, <Add YourName >
  • Date Proposed: 2015-1-15
  • Date Last Updated:
  • Priority:
  • Complexity:
  • Focus:

Current Situation

Meeting 2016-09-07: Should frequency be part of std.standard and have the same range as type time?!

The current real-world implementations of user defined physical types in VHDL are effectively tied to the implementation of integer which presents a problem with most tools only supporting 32-bit integers. The 31-bit positive range of the typical integer only covers nine orders of magnitude or three SI prefixes. This severely restricts the practicality of user defined physical types. The built-in time type is typically implemented as a 64-bit integer, covering a much more useful eighteen orders of magnitude.

Requirement

This proposal introduces a new requirement that user-defined physical types must capable of covering the same numeric range as the built-in time type. It is not concerned with providing an alternative integer type that bypasses the existing type system to get larger integers. The core issue is that there are effectively two classes of physical types in most VHDL implementations: one for time and the other for all other user-defined physical types. What is proposed here is a strict guarantee that user-defined physical types can be created with the same range as time regardless of the size of integer. This only affects physical types. An implementation that wants to stick with 32-bit integers can continue to do so. An implementation with 32-bit time will need to make no changes (or minimal in the case of the physical subtype).

There are a couple of ways that this can be implemented at the user visible level of the language:

1) New physical subtype

One implementation introduces a system where a new integral subtype physical is provided that is guaranteed to cover the same range as the built-in time type. It would be restricted to use only in definitions of physical types to eliminate problems with using it directly as a separate integral type in other contexts.

    -- Implicit subtype to be defined in std.standard
    subtype physical is $UNIVERSAL_INTEGER range time'pos(time'low) to time'pos(time'high);

2) Use existing syntax

The existing VHDL standard requires support for arbitrary sized integer literals as they are of type <universal integer>. This permits the declaration of larger physical types without needing any hacks like a magic physical type.

  type <name> is range time'pos(time'low) to time'pos(time'high) units ...  -- Borrowing the limits from time

  type <name> is range -(2**63-1) to 2**63-1 units ...  -- Setting limits using integer literals (Requires proper universal integer support)

  type <name> is range -9223372036854775807 to 9223372036854775807 units ...  -- Setting limits using integer literals without exceeding 63-bits

In practice most tools don't support this syntax as their implementation of user defined physical types doesn't accomodate ranges larger than the built-in integer. Even those with 64-bit integers may choke on intermediate calculations like "2**63" that cause a signed overflow. The third example to get around that problem is ugly and awkward. These are implementation details but a line item requirement that forces support for larger integers in this context is more likely to be heeded.

Implementation details

The proposal would require limited implementation effort. Most tools already have support for 64-bit time. Extending that same machinery to support other physical types should require little rework. All of the implicit operators would work as they do with time on implementations that retain 32-bit integers.

Code Examples

With today's tools one must compromise between precision and maximum range when defining a physical type:

    -- Definition of a "frequency" physical type using today's tools
    type frequency is range 0 to integer'high units
      Hz;
      kHz = 1000 Hz;
      MHz = 1000 kHz;
      GHz = 1000 MHz; -- Usually limited to 2**31 = 2.1 GHz
    end units;

We can make the primary unit kHz to extend the upper range while sacrificing the ability to represent low frequencies:

    type frequency is range 0 to integer'high units
      kHz;
      MHz = 1000 kHz;
      GHz = 1000 MHz;
      THz = 1000 GHz; -- Now support up to 2.1 THz
    end units;

It would be nice to have support for precision of mHz or better for more accurate representation of fractional frequencies. Using current implementations, though, a primary unit of mHz would limit the maximum frequency to 2.1 MHz which is too low for many practical uses of a frequency type.

With a 64-bit physical type range we can define a more useful frequency type that permits large values as well as accurately representing fractional frequencies:

    -- Implicit subtype to be defined in std.standard
    subtype physical is $UNIVERSAL_INTEGER range time'pos(time'low) to time'pos(time'high);

    type frequency is range 0 to physical'high units
      uHz;
      milliHz = 1000 uHz;
      Hz  = 1000 milliHz;
      kHz = 1000 Hz;
      MHz = 1000 kHz;
      GHz = 1000 MHz;
      THz = 1000 GHz; -- Up to 9.2 THz with a 63-bit positive range
    end units;

    -- Using "physical" elsewhere is prohibited
    variable not_allowed : physical;
    type invalid_array is array(physical'range) of bit;

Use Cases

This would be helpful in all situations where physical types are used. In online discussions it has been suggested that real type values can be used to perform most calculations. That approach loses the benefit of having the units marker to provide context to the preceding number. Literals of type real appear in the code as magic numbers that depend on supporting comments to identify what they represent. A physical type literal is much more useful as a means of documenting intent and is much more in keeping with the principles embodied in the language. Real-typed numbers also suffer from precision loss when dealing with values that exceed the significand size of the internal floating-point representation (typically 53-bits). It is possible that the implementation of real valued physical types could be borrowed from VHDL-AMS but this proposal is concerned with a minimal augmentation of the current system built around integral values.

The following is a snippet of real code used to implement an I2C slave. The I2C standard supports a range of clock frequencies and different setup times on the SDA line apply depending on the speed. This example permits the specification of the system clock frequency and the I2C bus frequency in their natural units where they can be easily verified by inspection. The internal clock cycle calculation uses various conversion functions to transform from the frequency typed generics to a synthesizable integer used to load a delay counter.

  generic (
    SYS_FREQ : frequency := 10 MHz;  -- Main system clock frequency
    I2C_FREQ : frequency := 100 kHz; -- Used to calculate SDA setup time

...

    -- When clock stretching we have to ensure the clock is released after meeting the
    -- min setup time on SDA.
    -- Min SDA setup times from I2C standard:
    -- Standard  (100 kHz):   250 ns
    -- Fast      (400 kHz):   100 ns
    -- Fast-Plus   (1 MHz):    50 ns

    -- Power regression on this data results in the approximation:
    -- min setup ~= 0.0007646 * (I2C clock period) ** 0.695899

    constant MIN_SDA_SETUP_CYCLES : natural := to_clock_cycles(0.0007646 * to_real(to_period(I2C_FREQ)) ** 0.695899, SYS_FREQ);

The typical values for the frequency generics in this case will fit within a 32-bit implementation but it serves to demonstrate that user-defined physical types are more than just an odd curiosity of the language and have real practical application.

Arguments FOR

User-defined physical types will gain equal footing with time with minimal changes to the language.

Arguments AGAINST

General Comments

Analysis

None of the examples given in Physical Type Ranges are legal VHDL today.

1) New physical subtype

$UNIVERSAL_INTEGER

An implementation may restrict the bounds of the range constraint of integer types other than type universal_integer. (5.2.3.1, para 8). You cannot create a subtype of universal_integer. No permission is given.

'POS

'POS is a pure function whose result type is universal_integer (16.2.2 Predefined attributes of types and objects). It's parameter is an expression of it's base type.

It's operand is another expression (9.3.1).

Execution of the function body results in a value of the type declared to be the result type in the declaration of the invoked function. (9.3.4 para 1).

It's an operation of type universal_integer (5.1, para 3. “attribute”).

It's a universal expression (9.5, para 1).

The result shall lie within the range of the integer type with the widest range provided by the implementation, excluding type universal_integer itself. (9.5, para 2).

The word shall is used to indicate a mandatory requirement (1.3.1, para 4).

2) Use existing syntax

Like the proposed physical subtype errors in these examples revolve around universal expressions, although numerical literals and implicitly defined subprogram descriptions have different details.

`POS

This is noted above in 1) New physical subtype... .The 'POS attribute is a basic operation and is required to conform to the limits of 9.5.

Numeric Literals

A numeric literal is an expression (9.3.2, para 3). It's a universal expression (9.5, para 1). It's value is the result of a basic operation (5.1, para 3). The basic operation's operand is the numeric literal (9.3.1). the resulting value is required to conform to the limits of 9.5.

Implicitly defined subprograms

We can short circuit a lot of the description of the impact by noting that in previous versions of the standard predefined operators were included explicitly as operations of a type (5.1) but likely inadvertently the reference to operators being included (where previous versions referenced 7.2 which is found as 9.2 in the current standard). That reference was removed from what is now 5.1 paragraph 2, which previously stated “The remaining operations of a type are the basic operations and the predefined operators (see ).” and now reads “The remaining operations of a type are the basic operations and the predefined operations (see 5.2.6, 5.3.2.4, 5.4.3, and 5.5.2).”. See the section History of "operations of a type".

If the predefined operators remain as members of the “set of operations of a type”, then the rule in 9.5 comes into play for the resulting value, also requiring it falls within the range of the widest integer type made available by the implementation excluding type universal_integer.

Also 9.5 paragraph 2: “The same operations are predefined for the type universal_integer as for any integer type. The same operations are predefined for the type universal_real as for any floating-point type. In addition, these operations include the following multiplication and division operators:”, the following table referring to two operators as operations, the two operators also found in 16.3 (see the LRM, 16.3 package Standard, bottom of page 258, top of page 259.). 16.3 also demonstrates declarations for predefined operations found in 5.2.6, 5.3.2.4, 5.4.3, and 5.5.2.

It's not sustainable to not refer to the predefined operat ors as an operation of a type. In practical terms there is not a single VHDL implementation available today that did not start out as compliant to a previous version of the standard under which the language of 5.1 paragraph 2 would have called these predefined operators operations of a type, causing invocation of the above mentio n rule in paragraph 4 of 9.5.

This implies that the range bound s of a type are limited by type INTEGER today.

Note that otherwise for a 64 bit universal_integer, 2**63 is not valid, as noted and neither would be 2**62 + (2**62 -1) which does fit but is not conforming to the limitation found limitation in 9.5 without an integer type other than universal_integer providing a wide enough range.

Solutions

A Direct Solution

A direct solution to both of these would be to increase the range of type INTEGER, or provide a new integer type definition in package STANDARD. The range of these would be implementation defined.

An Alternative Solution

Create a universal_physical type. This requires adding support in 9.5 for universal_physical, it's own set of predefined attributes for collisions with the 9.5 rules for universal_integer. These can be modifications of the text description for applicable type attributes returning type universal_integer.

It is an integer type other than for purposes of universal expressions, which means it would be explicitly excluded from a universal expression rule type universal_integer, supporting a limit of the widest range physical type made available by the implementation.

The range of a physical type is expressed in universal_physical, for which a basic operation from a numeric literal is required. The applicable sub-clause requiring modification is 5.2.3.1, paragraph 5.

This performs the same thing as the first proposal example without adding visible anonymous subtypes to anonymous type universal_integer.

Be aware there can be cases where performing arithmetic operations with types universal_integer or universal_real can result in an error (5.2.3.1, para 7) where the mathematical result doesn't match the arithmetic operator result. This limitation exists today with type TIME. This limitation would be evaded by using the direct solution.

History of “operations of a type”

The -2008 standard has no reference to predefined operators as being operations of a type in 5.1:

IEEE Std 1076-2008, 5. Types, 5.1 General, paragraph 2:

A type is characterized by a set of values and a set of operations. The set of operations of a type includes the explicitly declared subprograms that have a parameter or result of the type. The remaining operations of a type are the basic operations and the predefined operations (see 5.2.6, 5.3.2.4, 5.4.3, and 5.5.2). These operations are each implicitly declared for a given type declaration immediately after the type declaration and before the next explicit declaration, if any.

And such a reference existed in all previous versions of the standard:

IEEE Std 1076-2002, 3. Types, paragraph 2:

A type is characterized by a set of values and a set of operations. The set of operations of a type includes the explicitly declared subprograms that have a parameter or result of the type. The remaining operations of a type are the basic operations and the predefined operators (see 7.2). These operations are each implicitly declared for a given type declaration immediately after the type declaration and before the next explicit declaration, if any.

(Where 7.2 in -1987, -1993, -2000, -2002 corresponds to 9.2 Operators in -2008).

For instance in -2008 5.2.6 Predefined operations on scalar types, the operators now found in 9.2 are not referenced. The same holds true in -2008 5.3.2.4 Predefined operations on array types, etc.

The effect is to no longer include predefined operators now in 9.2 as operations of a type (in 5.1), a result that appears to be a mistake, in particular for scalar types.

Further P1076-2006-D2.1 3. Types, paragraph 2 demonstrates where in the revision process this omission likely occurred:

A type is characterized by a set of values and a set of operations. The set of operations of a type includes the explicitly declared subprograms that have a parameter or result of the type. The remaining operations of a type are the basic operations and the predefined operators (see 7.2). These operations are each implicitly declared for a given type declaration immediately after the type declaration and before the next explicit declaration, if any.

Noting that D3 was the Accellera approved version of the standard revision this appears to put the change eliminating support for implicitly declared predefined operators firmly in the -2008 standard release camp which revised the Accellera -2006 standard for purposes of conforming to IEEE standard format. There is no recoverable Language Change Specification containing the difference in the above cited paragraph and the corresponding paragraph in the -2008 standard.

It impacts IEEE Std 1076-2008 9.5 Universal expressions, paragraph 5 (counting the table as a paragraph):

For the evaluation of an operation of a universal expression, the following rules apply. If the result is of type universal_integer, then the values of the operands and the result shall lie within the range of the integer type with the widest range provided by the implementation, excluding type universal_integer itself. If the result is of type universal_real, then the values of the operands and the result shall lie within the range of the floating-point type with the widest range provided by the implementation, excluding type universal_real itself.

The use of "evaluation of an operation of a universal expression" binding to the "The of operations of a type include" (5.1, para 2). The language of 9.5 unchanged from 7.5 in -1987.

(Also note paragraph 2 of 9.5 implying operations includes operators).

The change to 5.1 paragraph 2 has the effect of not requiring the resulting value of a universal expression comprised of an operation specified in 9.2 that is an implicitly declared predefined operator to "lie within the integer type with the widest range provided by the implementation, excluding type universal_integer itself".

Note also that basic operations are so enclosed by 5.1 paragraph 2, preventing one from providing a numeric literal (an operand) with a value determined by it's value (a universal expression) outside the widest integer range excluding type universal_integer. You could take 'provided' in 9.5 paragraph 4 to require either explicit or implicit declaration (embracing the implicit type declarations of package STANDARD, where the operators specified in 9.2 are also implicitly declared) that is visible ("excluding type universal_integer itself").

The basic issue here is that the predefined operators are not explicitly declared and not mentioned directly or indirectly in 5.1.

The proper solution would appear to be to include 9.2 Operators in the list of predefined operations in 5.1. Clearly the intent until 5.1 was changed in the -2008 was to include predefined operators in the operations of a type.

A corrective action could be to modify the third sentence of the second paragraph of 5.1 to read:

… The remaining operations of a type are the basic operations, the predefined operations (see 5.2.6, 5.3.2.4, 5.4.3 and 5.5.2) and the predefined operators (see 9.2). ...

should there not be a sustainable reason for excluding predefined operators as operations of a type.

Addendum

Jim as recently made 1076-2008-D4.3a-annotated.pdf available on the private document space, and the annotations provide information on the LCS used in the change in 5.1 (LCS-2006-117). Without access to the LCS a search shows that it is responsible for adding the predefined operations found in 5.2.6 to packages numeric_std and numeric_bit, and likely provides no information on why 9.2 was eliminated from paragraph 2 of 5.1 noting that these operations are not predefined for types not declared in 16.3 (package STANDARD). See PDF page 45 of 1076-2008-D4.3a-annotated.pdf.

Supporters

Add your signature here to indicate your support for the proposal

-- Kevin Thibedeau -2015-01-14

-- PatrickLehmann - 2015-01-29

-- MartinZabel - 2016-03-01

Topic revision: r7 - 2020-02-17 - 15:34:36 - JimLewis
 
Copyright © 2008-2021 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback