Extended Ranges

Proposal Editing Information

Table of Content

Summary

VHDL has a concept of ranges for either defining ranges on ordered sets (integer ranges, floating point number ranges, ranges on enumerated types) or defining slices to get a sub-set of a composite type (array).

These ranges cannot be:

  • manipulated (moved/shifted by an offset, expanded, shrinked, combined/concatenated),
  • reversed, normalized,
  • compared (equal, inequal, left-of, right-of, smaller, bigger, ...),
  • queried for range properties (e.g. is_ascending, direction, length),
  • passed as an argument to a subprogram (procedure, function, operator), or
  • returned as a result from a function or (extended) user defined attribute.
This proposal addresses all these missing features, by
  • splitting ranges from subtype constraints,
  • introducing ranges as an immutable "object", which can be passed round,
  • defining operators on ranges to create a range arithmetic, and
  • defining attributes to ease the handling of ranges.

While developing a first solution, which could be summarized as "ranges as a new VHDL entity class" (Idea 1), I found some limitations in VHDL, which might imply too heavy LRM changes to fulfill all requirements. So I had a talk with Lieven Lemiengre, who offered another view to the topic, which could be summarized as "Ranges as an LRM pre-defined record" (Idea 2). This view solved many of my issues, but has some drawbacks, too. So this proposal will show two ideas, presenting advantages and disadvantages for both ideas and giving a starting point for a discussion in an upcoming IEEE meeting.

Finally, if my initial Idea 1 seems to be too pioneering, then there might be a hybrid solution...

Related and/or Competing Proposals:

RangeOperations - Allow static operations on "ranges".

  • Author: Stewart Cobb
  • VHDL Version: VHDL-93
  • Issue Number: IR-2072
  • Status: Forwarded
  • VASG-ISAC Analysis & Rationale: Some of the problems that the author mentions can be solved by using aliases to normalize ranges and directions of array objects. However, this proposal goes farther and permits some form of arithmetic on ranges.

Aliases and Subtypes as Ranges

Many discussions begin at or end up in a sentence like:
  • "Ranges can be emulated with subtypes.", or
  • "Many presented use cases can be worked around with aliases."
I agree that aliases and/or subtypes are a -- in my eyes -- dirty workaround to emulate ranges, because each constrained subtype of a scalar type has a range. Furthermore a subtype can be used as a discrete_subtype_indication. But a range is not a type. A range is an (implementation specific) data structure defining at least the following three members:
  • a direction,
  • a lower, and
  • an upper bound.
  • reference to an ordered set 1
A (sub-)type is more than a range. It defines:
  • a range (if constrained),
  • attributes for itself,
  • attributes for its instances, and
  • (default) operators for its instances.

1As Andy Jones pointed out in an IEEE reflector discussion, VHDL ranges need to be linked to an ordered set, otherwise a compiler can not know if for example red to blue is a valid range, a null range, etc. This might be trivial for integer and floating point literals, because these literals are of type universal_integer or universal_real and are compatible to each other. The problem comes from enumeration, which create new ordered sets, each with own position numbers. I'll deepen this point in a later paragraph.

General Requirements

Starting from an ideal world, this is a list of requirements, which shall be solved.
  • Mandatory:
    • ranges are created by the existing H downto L or L to H syntax
    • ranges are returned by the 'range and 'reverse_range attributes
    • ranges can be used in subtype declarations to constrain types
    • ranges can be used in slices
    • ranges can be used in choices
    • ranges can be passed into subprograms
      • as generic parameters
      • as formal parameters
    • ranges are immutable "objects" (they behave like constants)
    • ranges can be manipulated (the "manipulated" object is a new instance, because a range is immutable)
    • new shift/move operators << and >> for ranges
    • ranges can be reversed and normalized
    • ranges can be compared to each other
      • equality
    • properties of ranges can be queried
    • ranges can be aliased
  • Optional:
    • ranges can be returned from functions and attributes
    • ranges can be elements in composite types (e.g. to define multi-slices)
    • ranges are mutable (change the mandatory property of immutable ranges to mutable)

Current Situation - Ranges and Slices in VHDL

The range keyword is already reserved in VHDL and used in:
  • type declaration:
    • type short is range -32768 to 32767;
    • type ratio is range 0.0 to 1.0;
    • type string is array(positive range <>) of character;
  • subtype declarations:
    • subtype positive is integer range 1 to integer'high;
  • type marks (e.g. in a record declaration):
    • digit0 : integer range 0 to 9;
Additionally, 'range and 'reverse_range are pre-defined attribute names, containing the string "range".

Currently, VHDL's EBNF knows "ranges" as:

These ranges can be used in:
  • slices,
  • range constraints,
  • choices, or
  • for loops (including generate loops)

Definitions

VHDL Type System
  Scalar Type Ordered Type1 Discrete Type Numeric Types Has Position Number
integer type yes yes yes yes yes
enumeration type yes yes yes no yes
floating type yes yes no yes no
physical type yes yes no yes yes

1All relational operators are predefined.

Direction
A direction is an enumeration (enumerated type in VHDL) of 2 values: to and downto.
type direction is (to, downto);
Range
A range is an implementation specific data structure defining 3 members: 1) a lower bound, 2) an upper bound, and 3) a direction. The bounds are not limited to integer or floating point literals. Every scalar values is allowed. It can be expressed in (VHDL) pseudo code as a record:
type <name> is record
  lowerBound : <scalar_type>;
  upperBound : <scalar_type>;
  direction  : direction;
end record;

Proposal - Idea 1

A range should be a member in VHDL's entity class list and it should be a VHDL "object" with its own range_declaration rule. So a range becomes an named "object" like a VHDL constant or type. When a range becomes an object, its no longer an language internal intermediate data structure, which can now be (re)used e.g. in slices or constraints, be passed around, be returned, or define operators and have attributes.

So to warm-up with ranges, let's play with ranges and their range arithmetic. The range declartion syntax is inspired by the type declaration syntax.

Examples

EX01 - Simple Example 01
The following example declares two ranges: One in ascending (to) and one in descending (downto) order. This simple example uses a simple_range_expression as already known from VHDL-200x.

architecture a of e is
  range r_lower is 15 downto 0;
  -- range R_lower is range 15 downto 0;     -- too many range keywords in one VHDL line
  range r_upper is 16 to 31;
  range empty1  is null;                     -- creates a null range    (for arithmetic completeness)
  range empty2  is 0 downto 1;               -- results in a null range (current behavior)
begin
  -- ...;

EX02 - Simple Example 02
The following lines show arithmetic on ranges. Some ranges are created from attribute return values.

architecture a of e is
  subtype t_int3  is integer range 0 to 7;
  subtype t_slv_8 is std_logic_vector(7 downto 0);

  range LowerByteRange    is t_slv_8'range;                   -- store a subtype's range constraint in a range
  range UpperByteRange    is LowerByteRange << 8;             -- move (both) range bounds by +8 => 15 downto 8
  range WordRange         is LowerByteRange * 4;              -- expand range by 4, lower bound is not modified => 31 downto 0
  range HalfWordRange     is WordRange / 2;                   -- split the range in equal sized parts => 15 downto 0
  range DoubleWordRange1  is WordRange & WordRange;           -- combine two ranges => 63 downto 0
  range DoubleWordRange2  is UpperByteRange & WordRange;      -- combine two ranges => 39 downto 0
  range NormalizedRange   is UpperByteRange'normalized;       -- the range is moved so the lower bound is zero => 7 downto 0
  range ReverseRange      is UpperByteRange'reversed;         -- reverse direction => 8 to 15

  constant Bytes     : integer := WordRange / t_int3'range;   -- divide two range lengths => 4
  signal   MySignal  : std_logic_vector(WordRange);           -- constrain a array with a range
  subtype  MySubType is integer range WordRange;              -- constrain a type to a subtype
begin
  lowerByte <= MySignal(LowerByteRange);                      -- use a range in a slice
end architecture;

EX03 - Simple Example 03
This example shows a range in a generic list as well as in a formal parameter list.

function func
  generic   (range LHS);
  parameter (input : std_logic_vector(LHS); range RHS) return std_logic_vector is
begin
  return input(RHS);
end architecture;

-- example usage:
signal Registers : std_logic_vector(15 downto 0);
signal output    : std_logic_vector(3 downto 0);
function foo is new func generic map (LHS => Registers'range);
ouput <= foo(Registers, output'range);

EX04 - Complex Example 04
This complex example combines many aspects from the previous examples into one big example.

architecture a of e is
  -- 16-bit registers filled with four 3-bit status fields (S) and one error bit (E)
  signal Registers : std_logic_vector(15 downto 0);    -- Format: SSSE SSSE SSSE SSSE

  -- a generic extract function to demonstrate many capabilities of range objects
  function Extract
    generic   (type T; range R_MAX);
    parameter (input : T(R_MAX); range Slice)
  return std_logic_vector is
    variable result : T(Slice);
  begin
    result := input(Slice);
    return result;
  end function;

  range AllBitsRange    is 3 downto 0;
  range StatusRange     is 0 to 3;
  range StatusBitsRange is 3 downto 1;

  -- a normalized ranged is a to/downto range beginning at index 0
  signal Status : array(natural StatusRange'reverse) of std_logic_vector(StatusBitsRange'normalized);   -- (3 downto 0) of (2 downto 0)

  function GetStatus is new Extract
    generic map (T => Registers'type, R_Max => Registers'range);

begin
  genLoop: for i in StatusRange generate
    range CurrentStatusRange is StatusBitsRange'range << (i * AllBitsRange'length);
  begin
    Status(i) <= GetStatus(Registers, CurrentStatusRange);
  end generate;
end architecture;

This is an example to demonstrate what's possible with ranges and range arithmetic. It's not an example of short and compact VHDL code!

Detailed Requirements

  1. Ranges can be declared in any declarative region:
    • entity declarative region
    • architecture declarative region
    • package and package body
    • process statement
    • block statement
    • generate statement
    • protected type body
    • subprogram declarative region
  2. The existing syntax <expr> (to | downto) <expr> becomes a simple range expression and will construct a range.
  3. Ranges can be used as:
    • constraints,
    • slices
    • choices, or
    • loop-ranges as in the current usage.
  4. Ranges define attributes to query range properties:
    • 'low -> returns the lower bound
    • 'high -> returns the upper bound
    • 'left -> returns the left bound
    • 'right -> returns the right bound
    • 'length -> returns the length
    • 'direction -> returns the direction
    • 'is_ascending -> returns true if range is ascending
    • 'is_descending -> returns true if range is descending
    • 'is_nullrange -> returns true if range is a null range / empty
    • 'ascending -> returns a range with to direction
    • 'descending -> returns a range with downto direction
    • 'reverse -> reverses the direction
    • 'normalize -> creates a zero-based range (lower bound is zero)
    • 'image -> returns a string representation " to|downto "
  5. Ranges can be manipulated in complex range expressions by range arithmetic:
    • "<<" -> move bounds up
    • ">>" -> move bounds down
    • "+" -> extend upper bound
    • "-" -> shrink upper bound
    • "*" -> expand range
    • "/" -> split range
    • "/" -> range ratio
    • "&" -> combine ranges
  6. Ranges can implement set arithmetic:
    • "and" -> Intersection of two ranges
    • "or" -> Union of two ranges
    • "xor" -> Consecutive ranges
  7. Ranges can be compared for (in-)equality:
    • "=" -> Strict equality
    • "/=" -> Strict inequality
    • "?=" -> Matching equality
    • "?/=" -> Matching inequality
  8. The existing 'range and 'reverse_range attribute return ranges.
  9. Ranges can be passed in generic lists:
    • package generic lists
    • entity / component generic lists:
    • subprogram generic lists
  10. Ranges can be passed in formal lists to subprograms:
    • functions
    • procedures
    • operators
  11. Ranges can be returned by functions and attributes.

Changes in the EBNF Rules:

The following new or extended EBNF rules achieve the afore mentioned goals:
  • add range to entity class
  • define an incomplete_* and full_range_declartion as well as an interface_range_declaration
  • add range_declaration to block_*, entity_*, package_*, package_body_*, process_*, subprogram_* and protected_type_body_declarative_item
  • substitute simple_expression direction simple_expression with simple_range_expression
  • declare range_**_operator rules
  • declare complex_range_expression
+= means "add a further alternative"

entity_class +=
  | range

incomplete_range_declaration ::=
  range identifier ;

full_range_declartion ::=
  range identifier is complex_range_expression;

interface_incomplete_range_declaration ::=
  range identifier

interface_range_declaration ::=
  interface_incomplete_range_declaration

interface_declaration +=
  | interface_range_declaration

range_declartion ::=
  full_range_declartion

block_declarative_item +=
  | range_declartion

entity_declarative_item +=
  | range_declartion

package_declarative_item +=
  | range_declartion

package_body_declarative_item +=
  | range_declartion

process_declarative_item +=
  | range_declartion

subprogram_declarative_item +=
  | range_declartion

protected_type_body_declarative_item +=
  | range_declartion

simple_range_expression ::=
  simple_expression direction simple_expression

range ::=
    range_attribute_name
  | complex_range_expression

range_adding_operator ::= adding_operator
range_move_operator ::= << | >>
range_multiplying_operator ::= * | /
range_set_operator ::= and | or
range_relational_operator ::= = | /= | ?= | ?/=

range_operator ::=
    range_adding_operator
  | range_move_operator
  | range_multiplying_operator
  | range_set_operator

complex_range_expression ::=
    simple_range_expression
  | complex_range_expression range_operator simple_expression

relation ::=
    shift_expression [ relational_operator shift_expression ]
  | complex_range_expression range_relational_operator complex_range_expression

These production rules are not complete and might not be correct in terms of preserving VHDL's operator precedence.

Additions to STANDARD:

New Enumerated Type: direction

type direction is (ascending, descending);    -- to and downto cannot be used, because these are keywords

Attributes on Ranges

As VHDL has no syntax to map function to attributes, all attributes are defined as function with the prefix range_. See also: extended user-defined attributes
'low - return the lower bound
This is an implementation dependent attribute, accessing the internal range data structure and thus cannot be expressed as a function.
function range_low(range R) return universal_integer is
begin
  return <lower bound of R>;
end function;
'high - return the upper bound
This is an implementation dependent attribute, accessing the internal range data structure and thus cannot be expressed as a function.
function range_high(range R) return universal_integer is
begin
  return <upper bound of R>;
end function;
'left - return the left bound
function range_left(range R) return universal_integer is
begin
  if R'is_ascending then
    return R'low;
  else
    return R'high;
  end if;
end function;
'right - return the right bound
function range_right(range R) return universal_integer is
begin
  if R'is_ascending then
    return R'high;
  else
    return R'low;
  end if;
end function;
'length - return length
function range_length(range R) return universal_integer is
begin
  return R'high - R'low + 1;
end function;
'direction - return the direction
This is an implementation dependent attribute, accessing the internal range data structure and thus cannot be expressed as a function.
function range_direction(range R) return direction is
begin
  return <direction of R as an enum member>
end function;
'is_ascending - Returns true if range is ascending
function range_is_ascending(range R) return boolean is
begin
  return (R'direction = ascending);
end function;
'is_descending - Returns true if range is descending
function range_is_descending(range R) return boolean is
begin
  return (R'direction = descending);
end function;
'is_nullrange - Returns true if range is is a null range
function range_is_nullrange(range R) return boolean is
begin
  return (R'length <= 0);
end function;

'ascending - return a range with to direction

function range_ascending(range R) return range is
begin
  return R'low to R'high;
end function;
'descending - return a range with downto direction
function range_descending(range R) return range is
begin
  return R'high downto R'low;
end function;
'normalize - Create a zero-based range
function range_normalize(range R; zero : universal_integer := 0) return range is
begin
  return R - (R'low - zero);
end function;
'reverse - Reverse the direction
function range_reverse(range R) return range is
begin
  if R'is_ascending then
    return R'high downto R'low;
  else
    return R'low  to     R'high;
  end if;
end function;
'image - Returns a string representation
function range_image(range R) return string is
begin
  if R'is_ascending then
    return R'base'image(R'high) & " downto " & R'base'image(R'low);
  else
    return R'base'image(R'low)  & " to "     & R'base'image(R'high);
  end if;
end function;

Arithmetic Operators on Ranges

VHDL has four shift operators (sra, sla, srl, sll) and two rotate operators (ror, rol). Non of them is suitable to implement range shifting / moving, because a range shift is neither an arithmetic nor a logic operations as defined for binary shift operations. The meaning of the operators plus + and minus - can be used for range shifting, whereby plus means a constant offset is added to both ranges and minus means a constant offset is subtracted. I would prefer to have a new set of generic shift operators (<<, >>) for range operations. This might collide with external names syntax.

Every range is considered as a base range shifted by an offset. The lower bound of a base range is defined as zero. Every range can be converted to a base range by applying 'normalize on it. The behavior of most of the following operators is defined as if an operation is performed on a base range and the offset is restored after each operation. So most of the following operators effect only the upper bound, because the lower bound is zero by definition.

"<<" - Move a range up
function "<<"(range R; offset : universal_integer) return range is
begin
  if R'is_ascending then
    return R'low + offset  to     R'high + offset;
  else
    return R'high + offset downto R'low + offset;
  end if;
end function;
">>" - Move a range down
function ">>"(range R; offset : universal_integer) return range is
begin
  if R'is_ascending then
    return R'low - offset  to     R'high - offset;
  else
    return R'high - offset downto R'low - offset;
  end if;
  -- return R << (-offset);                                         -- alternative implementation
end function;
"+" - Increase the upper bound
function "+"(range R; offset : universal_integer) return range is
begin
  if R'is_ascending then
    return R'low           to     R'high + offset;
  else
    return R'high + offset downto R'low;
  end if;
end function;
"-" - Decrease the upper bound
function "-"(range R; offset : universal_integer) return range is
begin
  if R'is_ascending then
    return R'low           to     R'high - offset;
  else
    return R'high - offset downto R'low;
  end if;
  -- return R + (-offset);                                         -- alternative implementation
end function;
"*" - Expand a range
function "*"(range R; mult : natural) return range is
  constant offset : universal_integer := R'length * (mult - 1);
begin
  if R'is_ascending then
    return R'low           to     R'high + offset;
  else
    return R'high + offset downto R'low;
  end if;
  -- return R + offset;                                             -- alternative implementation
end function;
"/" - Split a range
function "/"(range R; div : positive) return range is
  constant length : natural := R'length / div;
begin
  if R'is_ascending then
    return R'low              to     R'low + length - 1;
  else
    return R'low + length - 1 downto R'low;
  end if;
end function;
"/" - Divide ranges to get the multiple of each other
function "/"(range LHS; range RHS) return integer is
begin
  assert (RHS'length                 > 0) report "RHS is an empty range."      severity failure;
  assert ((LHS'length mod RHS'length) = 0) report "LHS is not a multiple of RHS" severity failure;
  return LHS'length / RHS'length;
end function;
"&" - Combine two Ranges
--            RHS        LHS
function "&"(range LHS; range RHS) return range is
begin
  if R'is_ascending then
    return RHS'low             to     RHS'low + LHS'length;
  else
    return RHS'low + LHS'length downto RHS'low;
  end if;
  -- return RHS + LHS'length;                                         -- alternative implementation
end function;
This operator is not commutative, because only one range can be expanded by the length of the other range.

Relational Operations on Ranges

"=" - Strict equality
function "="(range LHS; range RHS) return boolean is
begin
  return (LHS'direction = RHS'direction)
     and (LHS'low       = RHS'low)
     and (LHS'high      = RHS'high);
end function;
"/=" - Strict inequality
function "/="(range LHS; range RHS) return boolean is
begin
  return not (LHS = RHS);
end function;
"?=" - Matching equality
function "?="(range LHS; range RHS) return boolean is
begin
  return (LHS'direction = RHS'direction)
     and (LHS'length    = RHS'length);
end function;
"?/=" - Matching inequality
function "?/="(range LHS; range RHS) return boolean is
begin
  return not (LHS ?= RHS);
end function;

Set Operations on Ranges

"and" - Intersection
function "and"(range LHS; range RHS) return range is
begin
  if    (LHS ?= RHS) then
    return LHS;
  elsif ((LHS'low <= RHS'high) and (LHS'high >= RHS'high) and (LHS'low >= RHS'low)) then
    if LHS'is_ascending then
      return LHS'low to RHS'high;
    else
      return RHS'high downto LHS'low;
    end if
  elsif ((RHS'low <= LHS'high) and (RHS'high >= LHS'high) and (RHS'low >= LHS'low)) then
    if LHS'is_ascending then
      return RHS'low to LHS'high;
    else
      return LHS'high downto RHS'low;
    end if
  else
    return NullRange;
  end if;
end function;
This operator is not commutative, because only range's direction can be preserved.
"and" - Is one range a subrange of the other?
function "and"(range LHS; range RHS) return boolean is
begin
  return not (LHS and RHS)'is_nullrange;
end function;
This operator is not commutative, because only range's direction can be preserved.
"or" - Union
function "or"(range LHS; range RHS) return range is
begin
  if    (LHS ?= RHS) then
    return LHS;
  elsif ((LHS'low <= RHS'high) and (LHS'high >= RHS'high) and (LHS'low >= RHS'low)) then
    if LHS'is_ascending then
      return RHS'low to LHS'high;
    else
      return LHS'high downto RHS'low;
    end if
  elsif ((RHS'low <= LHS'high) and (RHS'high >= LHS'high) and (RHS'low >= LHS'low)) then
    if LHS'is_ascending then
      return LHS'low to RHS'high;
    else
      return RHS'high downto LHS'low;
    end if
  else
    return NullRange;
  end if;
end function;
This operator is not commutative, because only range's direction can be preserved.
"or" - Are both ranges overlapping?
function "or"(range LHS; range RHS) return boolean is
begin
  return not (LHS or RHS)'is_nullrange;
end function;
This operator is not commutative, because only range's direction can be preserved.
"xor" - Consecutive Ranges
function "xor"(range LHS; range RHS) return range is
begin
  if ((LHS'low < RHS'low) and (LHS'high < RHS'low) and ((LHS'high + 1) = RHS'low)) then
    if LHS'is_ascending then
      return LHS'low to RHS'high;
    else
      return RHS'high downto LHS'low;
    end if
  elsif ((RHS'low < LHS'low) and (RHS'high < LHS'low) and ((RHS'high + 1) = LHS'low)) then
    if LHS'is_ascending then
      return RHS'low to LHS'high;
    else
      return LHS'high downto RHS'low;
    end if
  else
    return NullRange;
  end if;
end function;
This operator is not commutative, because only range's direction can be preserved.
"xor" - Are both ranges consecutive?
function "xor"(range LHS; range RHS) return boolean is
begin
  return not (LHS xor RHS)'is_nullrange;
end function;
This operator is not commutative, because only range's direction can be preserved.

Design Decisions

When a range is declared / created, the keyword is is used to delimit the range's name from the complex_range_expression. The proposal prefers is over := because a range is immutable.

Possible Issues

Renamed attributes
VHDL has an attribute called 'ascending, which returns a boolean value if a subtype's range is ascending. The proposal uses 'is_ascending to a) denote the boolean return value, b) use a common naming scheme as in other languages and c) free the attribute 'ascending for a new purpose: 'ascending returns a range in ascending order. The counter part 'descending returns a range in descending order.

VHDL has an attribute 'reverse_range to return a reversed range. The proposal uses 'reverse to spare the doubling of "range".

Open Questions (unsolved or for discussion)

Q01 - Move range bound with "+/-" or "sla/sra"?

Normally, I would suggest to use shift operators to modify the range bounds, but VHDL has no generic "<<" and ">>" operators, only sla, sra, sll, srl. So in my first draft I'll suggest plus (+) and minus (-) with the semantic of adding or subtracting an integer to/from both range bounds. Should VHDL get two new shift operators?

<Patrick Lehmann> Yes. </Patrick Lehmann>

Q02 - Can the unused (matching) relational operators be filled with semantic?

The proposal currently defines only equality and inequality, leaving 8 (4 strict and 4 matching) relational operators undefined. Is it possible to define a semantic like:
  • LHS is left-of/right-of RHS

<Brent Hayhoe> Investigate if relational operators can be expressed with the help of position numbers. </Brent Hayhoe>

Q03 - Should this proposal define set operations like intersection?

Ranges can be seen as sets. So it's possible to define set operations like intersection to return a new range or a boolean value.
  • LHS and RHS -> biggest partial range, being part of both ranges
  • LHS and RHS -> True if a partial range exists, which is part of LHS and RHS and which is not an empty range.

Is it possible to overload operators with different return types?

Q04 - Is an empty range useful?

VHDL can create null ranges or empty ranges. While it's possible to create an null range (0 downto 7), is it meaning full that a user can declare an empty range or is there only one pre-defined empty range, which can be used for comparison? I cannot think of any range arithmetic, which incorporates empty ranges.

<Patrick Lehmann> Yes, for semantic completeness. </Patrick Lehmann>

Q05 - How to define the operator precedence for range expressions?

R * 8 + 4 shall equal (R * 8) + 4, which should be equal to same priority to all range operators and left-associative.

Q06 - How should a concatenate (&) be defined?

The operator combine (&) is not symmetric/commutative: LHS & RHS / RHS & LHS=. How should concatenate be defined? Which side's lower bound and direction is used for the resulting range?

Q07 - Can operators and attributes be moved into a standard package?

Do we need to declare some / all operators and attributes in the LRM or can they be placed in one of the VHDL packages? Currently, no VHDL function or operator can return ranges or (sub-)types.

Q08 - Can floating point numbers be an index?

Do we really need to consider floating point numbers an index? On the other hand, floating point ranges are not restricted to indexing.

Q09 - Is it possible to overload operators based on their return type?

Is it possible to overload functions / operators based on their return type? This might be needed for the and operator, which can return a range intersection (-> range) or a intersection test result (-> boolean).

Proposal - Idea 2

The internal range data structure of a subtype should be accessible to the VHDL user through two new pre-defined attributes for any scalar type.
  • 'range_type returns an implicitly declared record type for the corresponding scalar type:
  • 'range_value returns a range instance of this implicit record type.
Ranges are expressed as subtypes and can be "converted" to the corresponding record with 'range_value. A ranged can be stored in a constant or variable, by defining the constant/variable of type subtype'range_type. As a range is stored in a normal VHDL object, it can be passed around. Further it's a normal -- but LRM-defined -- record and operators can be applied to it.

The LRM needs further changes to allow these pre-defined implicit records in slices, constraints and loops as an equivalent to the currently used discrete_range.

State-of-the-Art

Ranges can be expressed as (new) scalar types like:
type IntRange  is range 0 to 15;
type Realrange is range 0.0 downto 1.0;
type Enum is (Item0, Item1, Item2, Item3);

Every scalar type can be constrained by a range in a subtype declaration:

subtype SmallInt is integer range -32768 to 32767;
subtype Enum02   is Enum    range Item0  to Item2;

(Sub-)Types can be passed as type generics in _generic_=_interface_list=.

Implicit range Record:

Scalar types declare an implicit record (range_record), which represents a range as a triple: from, to, dir. The VHDL record prototype looks as follows:
type direction is (ascending, descending);    -- to and downto cannot be used, because these are keywords

type <unnamed> is record                -- the record is unnamed / anonymous
  lowerBound : <scalar_type>;           -- the bounds are not restricted to integers
  upperBound : <scalar_type>;
  direction  : direction;
end record;

The implicit record type of every scalar type can be accessed by an attribute called 'range_type. If a handle to the record type is required, it can be created with an alias declaration. A record instance can be created by the attribute called 'range_value.

subtype  R  is integer range 0 to 7;
alias integer_range is integer'range_type;
alias R_range       is R'range_type;                   -- LIEVEN: is this a different but closely related (record) type?
constant RLHS : integer_range      := R'range_value;    -- get R's range => 0 to 7
constant RRHS : R'base'range_type  := R'range_value;    -- equivalent declaration  LIEVEN: is this correct?

Detailed Requirements

  1. Ranges can be declared in any declarative region:
    • same as in idea 1
  2. The existing syntax <expr> (to | downto) <expr> becomes a simple range expression and will construct a range.
  3. Ranges can be used as:
    • constraints,
    • slices, or
    • loop-ranges as in the current usage.
  4. Ranges can be manipulated in complex range expressions by range arithmetic:
    • same as in idea 1
  5. Ranges can be compared:
    • same as in idea 1
  6. Ranges define attributes to query range properties:
    • ='low= -> returns the lower bound
    • ='high= -> returns the upper bound
    • ='left= -> returns the left bound
    • ='right= -> returns the right bound
    • ='length= -> returns length
    • ='direction= -> returns the direction
    • ='is_ascending= -> returns true if range is ascending
    • ='is_descending= -> returns true if range is descending
    • ='is_nullrange= -> returns true if range is a null range (empty)
    • ='ascending= -> returns a range with to direction
    • ='descending= -> returns a range with downto direction
    • ='reverse= -> reverse the direction
    • ='normalize= -> create a zero-based range (lower bound is zero)
    • ='image= -> returns a string representation
  7. The existing 'range and 'reverse_range attribute will return ranges.
  8. Ranges can be passed in generic lists:
    • same as in idea 1
  9. Ranges can be passed in formal lists to subprograms:
    • same as in idea 1
  10. Ranges can be returned by functions and attributes.

LIEVEN: Filling the next sections with content might enable stroked lines.

Additions to STANDARD:

New Enumerated Type: direction

type direction is (ascending, descending);    -- to and downto cannot be used, because these are keywords

One implicit declared range_record per scalar type:

type <unnamed> is record
  lowerBound : <scalar_type>;
  upperBound : <scalar_type>;
  direction  : direction;
end record;

Useful extensions if a user works with 'range_type.

alias integer_range is integer'range_type;
alias index_range   is integer_range;    -- alternative name, because most indices are integers

Attributes on Ranges

Currently, no attributes are proposed to query range properties. Some are accessible through the record itself, but most should be implemented in normal functions. (Extended user-defined attributes) might be a solution to map these functions to the range records.

Functions for Ranges

Lower Boundary

function low(R : RANGE_RECORD) return RANGE_TYPE is
begin
  return R.LowerBound;
end function;

Upper Boundary

function high(R : RANGE_RECORD) return RANGE_TYPE is
begin
  return R.UpperBound;
end function;

Left Boundary

function left(R : RANGE_RECORD) return RANGE_TYPE is
begin
  if is_ascending(R) then
    return R.LowerBound;
  else
    return R.UpperBound;
  end if;
end function;

Right Boundary

function right(R : RANGE_RECORD) return RANGE_TYPE is
begin
  if is_ascending(R) then
    return R.UpperBound;
  else
    return R.LowerBound;
  end if;
end function;

Length

function length(R : RANGE_RECORD) return INTEGER is
begin
  return R.UpperBound - R.LowerBound + 1;
end function;

Direction

function direction(R : RANGE_RECORD) return RANGE_DIRECTION is
begin
  return R.Direction;
end function;

Range is ascending

function is_ascending(R : RANGE_RECORD) return BOOLEAN  is
begin
  return (R.Direction = ASCENDING);
end function;

Range is descending

function is_descending(R : RANGE_RECORD) return BOOLEAN  is
begin
  return (R.Direction = DESCENDING);
end function;

Range is a null range

function is_nullrange(R : RANGE_RECORD) return BOOLEAN  is
begin
  return (length(R) <= 0);
end function;

Returns an ascending range

function ascending(R : RANGE_RECORD) return RANGE_RECORD is
begin
  return (
    LowerBound =>  R.LowerBound,
    UpperBound =>  R.UpperBound,
    Direction =>  ASCENDING
  );
end function;

Returns a descending range

function descending(R : RANGE_RECORD) return RANGE_RECORD is
begin
  return (
    LowerBound =>  R.LowerBound,
    UpperBound =>  R.UpperBound,
    Direction =>  DESCENDING
  );
end function;

Returns a reversed range

function reverse(R : RANGE_RECORD) return RANGE_RECORD is
begin
  if is_ascending(R) then
    return descending(R);
  else
    return ascending(R);
  end if;
end function;

Returns a normalized range

function normalize(R : RANGE_RECORD) return RANGE_RECORD is
begin
  return R srl length(R);
end function;

Returns a string representation

function image(R : RANGE_RECORD) return STRING is
begin
  if is_ascending(R) then
    return INTEGER'image(R.LowerBound) & " to "     & INTEGER'image(R.UpperBound);
  else
    return INTEGER'image(R.UpperBound) & " downto " & INTEGER'image(R.LowerBound);
  end if;
end function;

Operators on Ranges

"<<" - Move a range up

function range_move_up
  generic   (type T is scalar_type);
  parameter (R : T'range_type; offset : T) return T'range_type is
begin
  return (
    lowerBound => R.lowerBound + offset,
    upperBound => R.upperBound + offset,
    direction =>  R.direction
  );
end function;

-- example for an implicit operator creation for integer
function "<<" is new range_move_up generic map(T => integer);

">>" - Move a range down

function range_move_down
  generic   (type T is scalar_type);
  parameter (R : T'range_type; offset : T) return T'range_type is
begin
  return (
    lowerBound => R.lowerBound - offset,
    upperBound => R.upperBound - offset,
    direction =>  R.direction
  );
  -- return R << (-offset);                                         -- alternative implementation
end function;

-- example for an implicit operator creation for integer
function ">>" is new range_move_down generic map(T => integer);

"+" - Increase the upper bound

function range_increase
  generic   (type T is scalar_type);
  parameter (R : T'range_type; offset : T) return T'range_type is
begin
  return (
    lowerBound => R.lowerBound,
    upperBound => R.upperBound + offset,
    direction =>  R.direction
  );
end function;

-- example for an implicit operator creation for integer
function "+" is new range_increase generic map(T => integer);

"-" - Decrease the upper bound

function range_decrease
  generic   (type T is scalar_type);
  parameter (R : T'range_type; offset : T) return T'range_type is
begin
  return (
    lowerBound => R.lowerBound,
    upperBound => R.upperBound - offset,
    direction =>  R.direction
  );
  -- return R + (-offset);                                          -- alternative implementation
end function;

-- example for an implicit operator creation for integer
function "-" is new range_decrease generic map(T => integer);

"*" - Expand a range

function range_expand
  generic   (type T is scalar_type);
  parameter (R : T'range_type; mult : natural) return T'range_type is
  constant offset : integer := (R.upperBound - R.lowerBound + 1) * (mult - 1);
begin
  return (
    lowerBound => R.lowerBound,
    upperBound => R.upperBound + offset,
    direction =>  R.direction
  );
  -- return R + offset;                                             -- alternative implementation
end function;

-- example for an implicit operator creation for integer
function "*" is new range_expand generic map(T => integer);

"/" - Split a range

function range_split
  generic   (type T is scalar_type);
  parameter (R : T'range_type; div : positive) return T'range_type is
begin
  constant length : natural := (R.upperBound - R.lowerBound + 1) / div;
begin
  return (
    lowerBound => R.lowerBound,
    upperBound => R.lowerBound + length - 1,
    direction =>  R.direction
  );
end function;

-- example for an implicit operator creation for integer
function "/" is new range_split generic map(T => integer);

"/" - Divide ranges to get the multiple of each other

function range_div
  generic   (type T is scalar_type);
  parameter (LHS : T'range_type; RHS : T'range_type) return T'range_type is
begin
  return (LHS.upperBound - LHS.lowerBound + 1) / (RHS.upperBound - RHS.lowerBound + 1);
end function;

-- example for an implicit operator creation for integer
function "/" is new range_div generic map(T => integer);

"&" - Combine two ranges

function range_concat
  generic   (type T is scalar_type);
  parameter (LHS : T'range_type; RHS : T'range_type) return T'range_type is
  constant length : natural := (LHS.upperBound - LHS.lowerBound + 1);
begin
  return (
    lowerBound => RHS.lowerBound,
    upperBound => RHS.upperBound + length,
    direction =>  RHS.direction
  );
  -- return RHS + length;                                            -- alternative implementation
end function;

-- example for an implicit operator creation for integer
function "&" is new range_concat generic map(T => integer);

This operator is not commutative, because only one range's direction can be preserved.

Relational Operations on Ranges

"=" - Strict equality

TODO: needs upgrade
function "="(LHS : integer'range_type; RHS : integer'range_type) return boolean is
begin
  return LHS.direction  = RHS.direction and
         LHS.lowerBound = RHS.lowerBound and
         LHS.upperBound = RHS.upperBound;
end function;

"/=" - Strict inequality

TODO: needs upgrade
function "/="(LHS : integer'range_type; RHS : integer'range_type) return boolean is
begin
  return not (LHS = RHS);
end function;

"?=" - Matching equality

TODO: needs upgrade
function "?="(LHS : integer'range_type; RHS : integer'range_type) return boolean is
begin
  return LHS.direction  = RHS.direction and
         (LHS.upperBound - LHS.lowerBound) = (RHS.upperBound - RHS.lowerBound);
end function;

"?/=" - Matching inequality

TODO: needs upgrade
function "?/="(LHS : integer'range_type; RHS : integer'range_type) return boolean is
begin
  return not (LHS ?= RHS);
end function;

Set Operations on Ranges

"and" - Intersection
function "and"(LHS : RANGE_RECORD; RHS : RANGE_RECORD) return RANGE_RECORD  is
begin
  if (LHS ?= RHS) then
    return LHS;
  elsif ((LHS.LowerBound <= RHS.UpperBound) and (LHS.UpperBound >= RHS.UpperBound) and (LHS.LowerBound >= RHS.LowerBound)) then
    return (LHS.LowerBound, RHS.UpperBound, LHS.Direction);
  elsif ((RHS.LowerBound <= LHS.UpperBound) and (RHS.UpperBound >= LHS.UpperBound) and (RHS.LowerBound >= LHS.LowerBound)) then
    return (RHS.LowerBound, LHS.UpperBound, LHS.Direction);
  else
    return (
      LowerBound => 0,
      UpperBound => 0,
      Direction  => NULL_RANGE
    );
  end if;
end function;

This operator is not commutative, because only range's direction can be preserved.

"and" - Is one range a subrange of the other?
function "and"(LHS : RANGE_RECORD; RHS : RANGE_RECORD) return BOOLEAN  is
begin
  return not  is_nullrange(LHS or RHS);
end function;

This operator is not commutative, because only range's direction can be preserved.

"or" - Union
function "or"(LHS : RANGE_RECORD; RHS : RANGE_RECORD) return RANGE_RECORD  is
begin
  if (LHS ?= RHS) then
    return LHS;
  elsif ((LHS.LowerBound <= RHS.UpperBound) and (LHS.UpperBound >= RHS.UpperBound) and (LHS.LowerBound >= RHS.LowerBound)) then
    return (RHS.LowerBound, LHS.UpperBound, LHS.Direction);
  elsif ((RHS.LowerBound <= LHS.UpperBound) and (RHS.UpperBound >= LHS.UpperBound) and (RHS.LowerBound >= LHS.LowerBound)) then
    return (LHS.LowerBound, RHS.UpperBound, LHS.Direction);
  else
    return (
      LowerBound => 0,
      UpperBound => 0,
      Direction  => NULL_RANGE
    );
  end if;
end function;

This operator is not commutative, because only range's direction can be preserved.

"or" - Are both ranges overlapping?
function "or"(LHS : RANGE_RECORD; RHS : RANGE_RECORD) return BOOLEAN  is
begin
  return not  is_nullrange(LHS or RHS);
end function;

This operator is not commutative, because only range's direction can be preserved.

"xor" - Consecutive Ranges
function "xor"(LHS : RANGE_RECORD; RHS : RANGE_RECORD) return RANGE_RECORD  is
begin
  if ((LHS.LowerBound < RHS.LowerBound) and (LHS.UpperBound < RHS.LowerBound) and ((LHS.UpperBound + 1) = RHS.LowerBound)) then
    return (LHS.LowerBound, RHS.UpperBound, LHS.Direction);
  elsif ((RHS.LowerBound < LHS.LowerBound) and (RHS.UpperBound < LHS.LowerBound) and ((RHS.UpperBound + 1) = LHS.LowerBound)) then
    return (RHS.LowerBound, LHS.UpperBound, LHS.Direction);
  else
    return (
      LowerBound => 0,
      UpperBound => 0,
      Direction  => NULL_RANGE
    );
  end if;
end function;

This operator is not commutative, because only range's direction can be preserved.

"xor" - Are both ranges consecutive?
function "xor"(LHS : RANGE_RECORD; RHS : RANGE_RECORD) return BOOLEAN  is
begin
  return not  is_nullrange(LHS xor RHS);
end function;

This operator is not commutative, because only range's direction can be preserved.

Open Questions (unsolved or for discussion)

Q51 - Can 'range_value by replaced with 'range?

This proposal requests a special LRM rule, which allows a range_record in slices. So if every slice excepts a range_record, then 'range could always return a range_record instance (range_value). This in turn allows us to replace 'range_value with 'range.

Q52 - Is it possible to define attributes on ranges?

TODO

  • Review with Lieven. Search for TODO and LIEVEN.

Comparison of Idea 1 and Idea 2

Common Changes in both Ideas:

  • A new enumerated type direction is created in library std.
  • The already existing abstract entity range is made available to the user. The implementation dependent internal data structure of a range can be created at any place, be passed around and be manipulated.
  • No new keywords are required. If needed existing keywords are reused.
  • Operators known for their commutative property, can become non-commutative, which might be strange to some users. *

Idea 0 - Aliases and Subtypes as Ranges

Advantages:

  • Need to be listed.

Disadvantages:

  • Only a subset of the requested goals can be achieved.
  • Using subtypes to emulate ranges pollutes the namespaces with (heavy) types instead of (light) range instances.
  • Subtypes are carrying attributes. It's no good choice to add further range specific attributes, which are not meaningful as a type specific attributes.
  • Having operators on subtypes is stranger than having operators on ranges.

Idea 1 - Ranges as Objects

Advantages:

  • No syntactic and semantic break to existing code or to older VHDL revisions.
  • The syntax 7 downto 0 serves as a range constructor.
  • A range stays a special VHDL "object" like a type.
  • 'range and 'reverse_range return a range.
  • No need to change the behavior of slices, constraints and loops. A range can be used to constrain subtypes, select items or create new VHDL objects.
  • Handy, intuitive, compact syntax.
  • Property extraction from ranges is provided through VHDL attributes. *

Disadvantages:

  • Ranges cannot be returned by functions or (extended) user defined attributes.
  • Operators on ranges define a new category of expressions (range expressions).
  • Ranges cannot be part of composite types. *

Idea 2 - Ranges as Records

Advantages:

  • A range is a LRM-defined record and might need less LRM changes than a new "object" kind.
  • Range operations are defined as operators on record types.
  • Ranges can be used for any scalar type (incl. real and enumerated types)
  • Ranges can be passed into subprograms, because they are conventional records stored in existing VHDL objects (constant, variable).
  • Ranges can be return from functions or (extended) user defined attributes.
  • Ranges can be part of composite types (array, record).
  • Ranges can be immutable (constant) or mutable (variable).
  • A first alpha version of a RangePackage can be downloaded:

Disadvantages:

  • Using subtypes as ranges is not an aesthetic way
  • Subtypes are more than pure ranges. I don't like (sub-)types being misused as ranges. These are two semantic different concepts, because a range is part of a type's properties, but not wise versa.
  • Long attribute-based syntax. Preferred names are common names or reserved words.
  • Type pollution in the namespace.
  • 'range and 'reverse_range should return the implicit range Record -> big change.
  • Heavy LRM changes are needed to accept records in slices and constraints as a replacement or equivalent to the current ranges.
  • Property extraction from ranges needs functions. Currently, (extended) user defined attributes are not available. *

Additional Examples

Composites of Ranges

Imagine a communication controller for a user-defined protocol in a System-on-a-Chip, which offers a register interface. After several redesign cycles over the past 5 years, the configuration values are scattered over multiple register addresses. This happens if a designer didn't implement enough reserved bits or the projected design lifetime was exceeded. However, the configuration values as a sequence of config bits can be described as a array or record of ranges, which need to be accessed to gather the complete configuration value.

signal reg_interface : array(natural range 255 downto 0) of std_logic_vector(31 downto 0);

type Myrange is record
  RegID : natural;
  Rng   : range;
end record;
type MyRange_Vector is array(natural range <>) of MyRange;

function overall_range(RV : MyRange_Vector) return range is
  variable length : natural := 0;
begin
  for i in RV'range loop
    length = length + RV(i).Rng'length;
  end loop;
  return length - 1 downto 0;
end function;

constant capability_flags : MyRange_Vector := (
  0 => (0,  17 downto 4),
  1 => (25,  8 downto 0),
  2 => (97, 31 downto 8)
);

function extract(regs : std_logic_vector of std_logic_vector; positions : MyRange_Vector) return std_logic_vector is
  variable capabilities : std_logic_vector(overall_range(positions));
  variable offset       : natural := 0;
begin
  for i in positions'range loop
    capabilities(RV(i).Rng'normalized + offset) := regs(RV(i).RegID)(RV(i).Rng);
  end loop;
  return capabilities;
end function;

signal capabilities : std_logic_vector(overall_range(positions));

capabilities <= extract(reg_interface, capability_flags)

Constrained ports without generic parameters.

This example requires All Interface Lists Can Be Ordered. In 8b/10b encoding, every byte has an additional CharIsK bit, to distinguish data characters from K character and commas. Such encoders can be described for 1, 2, 4, or 8 bytes.
entity enc_8b10b is
  port (
    DataIn    : in  std_logic_vector;                                   -- must be a multiple of 8, otherwise the next line fails
    CharIsK   : in  std_logic_vector(DataIn'range / 8);                 -- require one K bit per input byte
    DataOut   : out std_logic_vector((DataIn'range / 4) & DataIn'range) -- emit 2 additional bits per byte (10 bits per 8 bit byte)
  );
end entity;

General Comments

<Brent Hayhoe> Some general comments on how I understand the range concept in VHDL works:

  • A range in VHDL is used to define a subtype.
  • We can reference a named range as a subtype and it therefore cannot be created as a new object. An object is associated with a subtype through its declaration. A type or a subtype is not an object but is defined as a member of the entity class
  • For the purposes of this proposal I will limit the range type to integer, but this is not so generally e.g. in std_logic_1164 an example of an enumerated range:
      subtype X01Z is resolved std_ulogic range 'X' to 'Z';
  • A better way to view an integer range definition may be using its positional value, defined as the anonymous predefined type called universal_integer and corresponds to its equivalent integer type value.
    An enumerated type is defined similarly except that its leftmost value is defined as position zero with only positive values for the rest of its elements.
  • Positional values are defined by the base type. The element value of a subtype has the same positional value as its base type element value.
    N.B. The leftmost positional value of an enumerated subtype is not necessarily zero!
  • The type integer is a discrete type (as are enumerated types), ranges are not limited to this group, although some of the operator functions (sll,slr) will be.
  • Many people (myself included) use range declarations in a structural manner:
      subtype my_range_jt is natural range 10 downto 0;

      subtype my_vector_vt    is std_logic_vector(my_range_jst);
      subtype my_signed_svt   is signed(my_range_jst);
      subtype my_unsigned_uvt is unsigned(my_range_jst);
  • In the example above, the range 'my_vector_vt' can be viewed as a mathematically ordered set defined as:
      {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0} with position numbers 10 down to 0
  • As can be seen, the position number remains constant irrespective of direction and defined by the base type and with its order is controlled by the to or downto reserved words.
  • I like the idea of defining new shift operators "<<" and ">>". These need to be of a new function type similary to the new map function proposed for interface support. Let's define it as a set function, or type function, or range function, which instead of returning a subtype value, will return the complete subtype set of values.
      subtype my_range1_jt is natural range 9 downto 7;
      -- defines a set {9, 8, 7}
      subtype my_range2_jt is my_range1_jt >> 2;
      -- produces a set {7, 6, 5}
      subtype my_range3_jt is my_range1_jt << 1;
      -- produces a set {10, 9, 8}
      subtype my_range4_jt is my_range1_jt << 2;
      -- generates an error
  • The new range function will return a subtype and would look something like this:
      range function "<<"
         generic(type L)
         parameter(R : integer) 
            is
         constant lo_pos : universal_integer := L'pos(L'low);
         constant hi_pos : universal_integer := L'pos(L'high);
      begin
         if L'ascending then
            return subtype L'base range L'base'val(lo_pos - R) to     L'base'val(hi_pos - R);
         else
            return subtype L'base range L'base'val(hi_pos + R) downto L'base'val(lo_pos + R);
         end if
      end range function

      range function "<<"
         generic(type R)
         parameter(L : integer) 
            is
      begin
         return subtype R << L;
      end range function
  • The shift operators effectively manipulate both top and bottom of the range. We also require a means of individually performing operations on each of these top & bottom elements separately.
    my_range+2 and 2+my_range might serve this purpose as could my_range/2 but 2/my_range is just wrong!
  • We need operators that can work on either left or right bound and both bounds of the range.
  • Define new operators ">+", "+<", ">+<", etc
  • We can define some further operations on sets:
    my_range   {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
       
    Operation Definition Return Subtype
    my_range >> 2 shift right {8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2}
    2 >> my_range ditto {8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2}
    2 << my_range shift left {12, 11 10, 9 8, 7, 6, 5, 4, 3, 2}
    my_range << 2 ditto {12, 11 10, 9 8, 7, 6, 5, 4, 3, 2}
       
    my_range >+ 2 add to right bound {10, 9, 8, 7, 6, 5, 4, 3, 2}
    my_range +> 2 ditto {10, 9, 8, 7, 6, 5, 4, 3, 2}
    my_range <+ 2 add to left bound {12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
    my_range +< 2 ditto {12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
    my_range >+< 2 add to both bounds {12, 11 10, 9 8, 7, 6, 5, 4, 3, 2}
    my_range <+> 2 ditto {12, 11 10, 9 8, 7, 6, 5, 4, 3, 2}
       
    my_range >- 2 subtract from right bound {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2}
    my_range -> 2 ditto {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2}
    my_range <- 2 subtract from left bound {8, 7, 6, 5, 4, 3, 2, 1, 0}
    my_range -< 2 ditto {8, 7, 6, 5, 4, 3, 2, 1, 0}
    my_range >-< 2 subtract from both bounds {8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2}
    my_range <-> 2 ditto {8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2}
       
    my_range >* 2 multiply right bound {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
    my_range *> 2 ditto {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
    my_range <* 2 multiply left bound {20, 19, 18, 17, 16, 15, 14, 13, 12, 11,
    10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
    my_range *< 2 ditto {20, 19, 18, 17, 16, 15, 14, 13, 12, 11,
    10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
    (my_range >+ 2) >*< 2 multiply both bounds {20, 19, 18, 17, 16, 15, 14, 13, 12, 11,
    10, 9, 8, 7, 6, 5, 4}
    (my_range >+ 2) <*> 2 ditto {20, 19, 18, 17, 16, 15, 14, 13, 12, 11,
    10, 9, 8, 7, 6, 5, 4}
       
    my_range >/ 2 divide right bound {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
    my_range /> 2 ditto {5, 4, 3, 2, 1, 0}
    my_range </ 2 divide right bound {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
    my_range /< 2 ditto {5, 4, 3, 2, 1, 0}
    (my_range >+ 2) >/< 2 divide both bounds {5, 4, 3, 2, 1}
    (my_range >+ 2) </> 2 ditto {5, 4, 3, 2, 1}
       

    </Brent Hayhoe>

    Arguments FOR

    Arguments AGAINST

    Supporters

    -- Patrick Lehmann - 2016-07-19

    -- Brent Hayhoe - 2016-07-21

    Add your signature here to indicate your support for the proposal

Topic revision: r1 - 2017-02-22 - 07:08:36 - PatrickLehmann
 
Copyright © 2008-2024 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback