I get this error message:
testbench.vhd:16:22: no function declarations for operator "+"
at this line:
Z <= unsigned(X) + resize(unsigned(Y),X'length);
with this code:
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
entity MCVE is
end entity MCVE;
architecture MCVE of MCVE is
signal X, Z : std_logic_vector(15 downto 0);
signal Y : std_logic_vector(7 downto 0);
begin
process
begin
Z <= unsigned(X) + resize(unsigned(Y),X'length);
end process;
end architecture MCVE;
https://www.edaplayground.com/x/2LBg
I don't understand why.
The error message is pretty clear. There is no function declarations for operator "+" which can add two unsigneds together and return a std_logic_vector. In the package numeric_std, there is however an opertor "+" which can add two unsigneds together and return an unsigned.
So, if you add another type conversion to convert the result of the addition back to a std_logic_vector the compiler can choose the version of the "+" operator that returns an unsigned.
Z <= std_logic_vector(unsigned(X) + unsigned(Y));
Overloading only works if there is exactly one fit for function, procedure or operator. If there is less than one, then there is no version for the compiler to choose; if there is more than one, the compiler doesn't know which one to choose and there is an ambiguity which needs to be sorted out.
Actually, you don't need to resize Y: the "+" operator is happy as long as one of the operands is the same width as the result.
https://www.edaplayground.com/x/4VJE
While Giwrgos Rizeakos original question was on resizing, there's a long history of questions and answers on operator and function overload visibility (there no function declaration visible here for operator "+").
After a search through those questions and answers this answer attempts to provide a question and answer pair that can be used to eliminated future duplicate questions based on providing authoritative references in an explanatory flow.
The problem starts with visibility:
12.3 Visibility
The meaning of the occurrence of an identifier at a given place in the text is defined by the visibility rules and also, in the case of overloaded declarations, by the overloading rules. The identifiers considered in this subclause include any identifier other than a reserved word or an attribute designator that denotes a predefined attribute. The places considered in this subclause are those where a lexical element (such as an identifier) occurs. The overloaded declarations considered in this subclause are those for subprograms and enumeration literals.
For each identifier and at each place in the text, the visibility rules determine a set of declarations (with this identifier) that define the possible meanings of an occurrence of the identifier. A declaration is said to be visible at a given place in the text when, according to the visibility rules, the declaration defines a possible meaning of this occurrence. The following two cases arise in determining the meaning of such a declaration:
— The visibility rules determine at most one possible meaning. In such a case, the visibility rules are sufficient to determine the declaration defining the meaning of the occurrence of the identifier, or in the absence of such a declaration, to determine that the occurrence is not legal at the given point.
— The visibility rules determine more than one possible meaning. In such a case, the occurrence of the identifier is legal at this point if and only if exactly one visible declaration is acceptable for the overloading rules in the given context or all visible declarations denote the same named entity.
It involves overloaded subprograms:
4.5.2 Operator overloading
The declaration of a function whose designator is an operator symbol is used to overload an operator. The sequence of characters of the operator symbol shall be one of the operators in the operator classes defined in 9.2.
The subprogram specification of a unary operator shall have a single parameter, unless the subprogram specification is a method (see 5.6.2) of a protected type. In this latter case, the subprogram specification shall have no parameters. The subprogram specification of a binary operator shall have two parameters, unless the subprogram specification is a method of a protected type, in which case, the subprogram specification shall have a single parameter. If the subprogram specification of a binary operator has two parameters, for each use of this operator, the first parameter is associated with the left operand, and the second parameter is associated with the right operand.
Operator overloads are defined as subprograms and use subprogram overloading rules:
4.5 Subprogram overloading
4.5.1 General
Two formal parameter lists are said to have the same parameter type profile if and only if they have the same number of parameters, and if at each parameter position the corresponding parameters have the same base type. Two subprograms are said to have the same parameter and result type profile if and only if both have the same parameter type profile, and if either both are functions with the same result base type or neither of the two is a function.
As a segue subprograms can be described in terms of signatures as a shorthand:
4.5.3 Signatures
A signature distinguishes between overloaded subprograms and overloaded enumeration literals based on their parameter and result type profiles. A signature can be used in a subprogram instantiation declaration, attribute name, entity designator, or alias declaration.
signature ::= [ [ type_mark { , type_mark } ] [ return type_mark ] ]
(Note that the initial and terminal brackets are part of the syntax of signatures and do not indicate that the entire right-hand side of the production is optional.) A signature is said to match the parameter and the result type profile of a given subprogram if, and only if, all of the following conditions hold:
— The number of type marks prior to the reserved word return, if any, matches the number of formal parameters of the subprogram.
— At each parameter position, the base type denoted by the type mark of the signature is the same as the base type of the corresponding formal parameter of the subprogram.
— If the reserved word return is present, the subprogram is a function and the base type of the type mark following the reserved word in the signature is the same as the base type of the return type of the function, or the reserved word return is absent and the subprogram is a procedure.
And here the example:
Z <= unsigned(X) + resize(unsigned(Y),X'length);
can be described in terms of a signature.
Z is declared as type std_logic_vector. The left operand to "+" is X which is subject type conversion to unsigned (rules given in 9.3.6, both array types, same element type). The right operand is the result of resize with a signature of [unsigned, natural, return unsigned] found in package numeric_std by overload resolution:
12.5 The context of overload resolution
Overloading is defined for names, subprograms, and enumeration literals.
...
When considering possible interpretations of a complete context, the only rules considered are the syntax rules, the scope and visibility rules, and the rules of the form as follows:
a) Any rule that requires a name or expression to have a certain type or to have the same type as another name or expression.
b) Any rule that requires the type of a name or expression to be a type of a certain class; similarly, any rule that requires a certain type to be a discrete, integer, floating-point, physical, universal, or character type.
...
e) The rules given for the resolution of overloaded subprogram calls; for the implicit conversions of universal expressions; for the interpretation of discrete ranges with bounds having a universal type; for the interpretation of an expanded name whose prefix denotes a subprogram; and for a subprogram named in a subprogram instantiation declaration to denote an uninstantiated subprogram.
...
And the function made visible through the use of use clause making the declarations in numeric_std visible:
12.4 Use clauses
A use clause achieves direct visibility of declarations that are visible by selection.
The same overload resolution is performed on the example where no function declaration is found. No function declaration is found for "+" [unsigned, unsigned return std_logic_vector] where the return value must match type std_logic_vector (the a) rule above).
And as Matthew Taylor points out you can alter the assignment right hand expression by type conversion to std_logic_vector:
Z <= std_logic_vector(unsigned(X) + resize(unsigned(Y),X'length));
And why the resize function call is not needed can authoritatively be shown by referencing the function declaration for "+" `[unsigned, unsigned return unsigned] found in IEEE package numeric_std, (found in numeric_std-body.vhdl, in the 1076-2008 downloads.zip which is part of the -2008 standard):
-- Id: A.3R
function "+" (L : UNRESOLVED_UNSIGNED; R : STD_ULOGIC)
return UNRESOLVED_UNSIGNED
is
variable XR : UNRESOLVED_UNSIGNED(L'length-1 downto 0) := (others => '0');
begin
XR(0) := R;
return (L + XR);
end function "+";
Interpreting that authoritatively requires the the declaration for unsigned in numeric_std.vhdl (also found in the same above zip file):
type UNRESOLVED_UNSIGNED is array (NATURAL range <>) of STD_ULOGIC;
subtype UNSIGNED is (resolved) UNRESOLVED_UNSIGNED;
which is a subtype of unresolved_unsigned providing an element resolution function name. Resolution functions are explained in the standard:
4.6 resolution functions
A resolution function is a function that defines how the values of multiple sources of a given signal are to be resolved into a single value for that signal. Resolution functions are associated with signals that require resolution by including the name of the resolution function in the declaration of the signal or in the declaration of the subtype of the signal. A signal with an associated resolution function is called a resolved signal (see 6.4.2.3).
and the subtype declaration syntax can require a resolution function be found:
6.3 Subtype declarations
subtype_declaration ::=
subtype identifier is subtype_indication ;
subtype_indication ::=
[ resolution_indication ] type_mark [ constraint ]
resolution_indication ::=
resolution_function_name | ( element_resolution )
element_resolution ::= array_element_resolution | record_resolution
and function resolved is found in the the std_logic_1164 package declaration (in the same above zip file, provided as part of the standard).
Resolution is a matter for simulation, see 14.7 Execution of a model, particularly 14.7.3 Propagation of signal values and it's sub-clauses where resolution is applied to signals.
(And about here you see why there can be a lot of incomplete answers that don't preclude future questions. Finding answers in the standard requires subject matter understanding and it's primary audiences are tool implementors and advanced users where VHDL syntax and semantics are defined concisely allowing it's use as a formal notation.)
Related
I use a TCL script to generate a VHDL pkg for each submodule repository that stores the compilation date time and git hash of the submodule. The constant storing 32 bit git hash is called MAIN_GIT_HASH and is an std_logic_vector.
I now have multiple packages that all contain a constant called MAIN_GIT_HASH and I need to include them into the same source file using the VHDL "use" directive. Now the question is, what is the correct way to select the correct constant from each package since just writing "MAIN_GIT_HASH" will be ambigious?
Each of the packages either needs to have a unique name or needs to be analyzed into a separate library.
If you use separate libraries, you simply reference it using a package path:
library Submodule1 ;
use Submodule1.HashPkg.MAIN_GIT_HASH ; -- just get the relevant constant
Should a design need to reference more than one HashPkg (like your use case), then you can reference the constant with the same notation as above:
HashReg <= Submodule1.HashPkg.MAIN_GIT_HASH ;
If you use unique named packages, then they could all be put into the same library. Assuming that that library is the same as the one into which you are compiling the current design unit, then you can reference it as being in work:
use work.Submodule1HashPkg.MAIN_GIT_HASH ;
. . .
HashReg <= work.Submodule1HashPkg.MAIN_GIT_HASH ;
Use selected names.
The cause:
IEEE Std 1076-2008
12.4 Use clauses
In order to determine which declarations are made directly visible at a given place by use clauses, consider the set of declarations identified by all use clauses whose scopes enclose this place. Any declaration in this set is a potentially visible declaration. A potentially visible declaration is actually made directly visible except in the following three cases:
a) A potentially visible declaration is not made directly visible if the place considered is within the immediate scope of a homograph of the declaration.
b) If two potentially visible declarations are homographs and one is explicitly declared and the other is implicitly declared, then the implicit declaration is not made directly visible.
c) Potentially visible declarations that have the same designator and that are not covered by case b) are not made directly visible unless each of them is either an enumeration literal specification or the declaration of a subprogram.
12.3 Visibility
...Each of two declarations is said to be a homograph of the other if and only if both declarations have the same designator, and they denote different named entities, and either overloading is allowed for at most one of the two, or overloading is allowed for both declarations and they have the same parameter and result type profile (see 4.5.1).
12.5 The context of overload resolution
12.5 The context of overload resolution
Overloading is defined for names, subprograms, and enumeration literals.
Your constant declarations are not homographs, not enumeration literals nor declarations of subprograms. None of the multiple declarations of constants with the same name would be directly visible.
4.7 Package declarations
Items declared immediately within a simple or a generic-mapped package declaration become visible by selection within a given design unit wherever the name of that package is visible in the given unit. Such items may also be made directly visible by an appropriate use clause (see 12.4). Items declared immediately within an uninstantiated package declaration cannot be made visible outside of the package.
The cure:
back to 12.3:
Visibility is either by selection or direct. A declaration is visible by selection at places that are defined as follows:
...
f) For a declaration given in a package declaration, other than in a package declaration that defines an uninstantiated package: at the place of the suffix in a selected name whose prefix denotes the package.
...
8.3 Selected names
A selected name is used to denote a named entity whose declaration appears either within the declaration of another named entity or within a design library.
selected_name ::= prefix . suffix
suffix ::=
simple_name
| character_literal
| operator_symbol
| all
A selected name can denote an element of a record, an object designated by an access value, or a named entity whose declaration is contained within another named entity, particularly within a library, a package, or a protected type. Furthermore, a selected name can denote all named entities whose declarations are contained within a library or a package.
If we peruse 8. Names, 8.1 General we find that a prefix can be a name, including a selected name.
A code example:
library ieee;
use ieee.std_logic_1164.all;
package pkg1 is
constant MAIN_GIT_HASH: std_logic_vector := x"ffffffff";
end pkg1;
library ieee;
use ieee.std_logic_1164.all;
package pkg2 is
constant MAIN_GIT_HASH: std_logic_vector := x"00000000";
end pkg2;
library ieee;
use ieee.std_logic_1164.all;
use work.pkg1.all;
use work.pkg2.all;
entity foo is
end entity;
architecture fum of foo is
function to_string (inp: std_logic_vector) return string is -- before -2008
variable image_str: string (1 to inp'length);
alias input_str: std_logic_vector (1 to inp'length) is inp;
begin
for i in input_str'range loop
image_str(i) := character'VALUE(std_ulogic'IMAGE(input_str(i)));
end loop;
return image_str;
end function;
begin
process
begin
report LF & HT & " MAIN_GIT_HASH = " & to_string(work.MAIN_GIT_HASH);
report LF & HT & "pkg1 MAIN_GIT_HASH = "
& to_string(work.pkg1.MAIN_GIT_HASH);
report LF & HT & "pkg2 MAIN_GIT_HASH = "
& to_string(work.pkg2.MAIN_GIT_HASH);
wait;
end process;
end architecture;
where
ghdl:error: compilation error
%: ghdl -a multiple_packages.vhdl
multiple_packages.vhdl:37:63:error: unit "main_git_hash" not found in library "work"
report LF & HT & " MAIN_GIT_HASH = " & to_string(work.MAIN_GIT_HASH);
^
ghdl:error: compilation error
%:
ghdl isn't very descriptive (doesn't track visibility in it's name look up to tell us why it isn't visible) while Modelsim may be more expressive, something along the lines of:
Error (10621): VHDL Use Clause error at multiple_packages.vhdl(37): more than one Use Clause imports a declaration of simple name "MAIN_GIT_HASH"
This error shows up a lot where users provide use clauses for both IEEE package numeric_std and Synopsys package std_logic_arith.
Commenting out line 37 we can demonstrate the use of selected names where direct visibility is not available:
ghdl:error: compilation error
%: ghdl -a multiple_packages.vhdl
%: ghdl -e foo
%: ghdl -r foo
multiple_packages.vhdl:38:9:#0ms:(report note):
pkg1 MAIN_GIT_HASH = 11111111111111111111111111111111
multiple_packages.vhdl:40:9:#0ms:(report note):
pkg2 MAIN_GIT_HASH = 00000000000000000000000000000000
%:
Note with a use work.all; making the package names visible the selected name prefixes could be simple_names instead of themselves being selected names.
I have an hierarchy created by a for-generate like this:
INST: for ... generate
. . .
end generate;
It creates many instances, as expected, named as INST__0, INST__1, etc, which names have a double underscore on it.
When I try to create an alias to a signal A in this hierarchy I got an error like "Invalid literal" because the path to the signal has double underscores, which is indeed invalid in VHDL:
alias A is <<signal DUT.INST__0.COUNTER.A: std_logic>>;
Is there any way to solve this problem? Prevent for-generate using double underscores, maybe?
Thanks
You do not provide a reproducible or complete example nor demonstrate the syntax location of your alias declaration. Particular note the lack of a the loop parameter which your attempt alludes to include values 0 and 1.
See IEEE Std 1076-2008 8.7 External names
pathname_element ::=
entity_simple_name
| component_instantiation_label
| block_label
| generate_statement_label [ ( static_expression ) ]
| package_simple_name
and the accompanying semantic description of the the static expression:
b)Second, for each package simple name in a package pathname, or for each pathname element in an absolute or relative pathname, in order, the previously identified declarative region is replaced as the identified declarative region by one of the following:
...
5)For a generate statement label, the declarative region of the equivalent block corresponding to the generate statement. If the generate statement is a for generate statement, the pathname element shall include a static expression, the type of the expression shall be the same as the type of the generate parameter, and the value of the expression shall belong to the discrete range specified for the generate parameter. The type of the expression shall be determined by applying the rules of 12.5 to the expression considered as a complete context, using the rule that the type shall be discrete. If the type of the expression is universal_integer and the type of the generate parameter is an integer type, an implicit conversion of the expression to the type of the generate parameter is assumed.
We see that the static expression included in parentheses following the generate statement label is a value of the loop parameter.
A -2008 example that can be analyzed, elaborated and simulated:
entity for_gen_label is
end entity;
architecture fum of for_gen_label is
begin
INST:
for i in 0 to 3 generate
COUNTER:
block
signal a: boolean;
begin
PROC_LABEL:
process
begin
report a'INSTANCE_NAME;
wait;
end process;
end block;
end generate;
end architecture;
where we also see that the -2008 predefined attribute 'INSTANCE_NAME can also demonstrate path name elements (GHDL):
for_gen_label.vhdl:16:17:#0ms:(report note): :for_gen_label(fum):inst(0):counter:a
for_gen_label.vhdl:16:17:#0ms:(report note): :for_gen_label(fum):inst(1):counter:a
for_gen_label.vhdl:16:17:#0ms:(report note): :for_gen_label(fum):inst(2):counter:a
for_gen_label.vhdl:16:17:#0ms:(report note): :for_gen_label(fum):inst(3):counter:a
The format of the 'INSTANCE_NAME predefined attribute value is given in 16.2.5 Predefined attributes of named entities.
The two underscores is a C(++) affectation for names which indicates you're probably getting information from the user interface of a simulator capable of supporting multiple hardware description languages. GHDL, a batch simulator supporting only VHDL produces output that adheres to VHDL path name elements:
ghdl -r for_gen_label --disp-signals-map
.for_gen_label(fum).inst(0).counter.a: 00007FBDBD504700 net: 0
.for_gen_label(fum).inst(1).counter.a: 00007FBDBD5047A0 net: 0
.for_gen_label(fum).inst(2).counter.a: 00007FBDBD504840 net: 0
.for_gen_label(fum).inst(3).counter.a: 00007FBDBD5048E0 net: 0
...
while incidentally demonstrating the entire path. VHDL unlike some other HDL's is not identifier case sensitive.
I would try an extended identifier. I would try both of the following, but I suspect the first one will get you there (and the second one not):
alias A is <<signal DUT.\INST__0\.COUNTER.A : std_logic>>;
and
alias A is <<signal \DUT.INST__0.COUNTER.A\ : std_logic>>;
I am concerned though the second one would be seen as a single identifier.
I use this code in a testbench, which works as expected:
std_logic_vector(to_unsigned(integer(0.603205*(2**16)),16))
i want to replace it with a function defined in the same file:
function convert_mult(mult : real) return std_logic_vector is
begin
return std_logic_vector(to_unsigned(integer(mult*(2**16)),16));
end function;
and call it like this:
convert_mult(0.603205)
the function fails compilation with "Cannot find the "*" operator with operands denoted with the "[REAL, UNIVERSAL_INTEGER]" signature."
I can't work out whats wrong with this, i thought real * integer was supported? should i be using another type?
TL;DR
You can mix real and integer literals like in 0.603205*(2**16) but not non-literal reals and integer literals like in mult*(2**16). As noted by Matthew, real and integer are closely related types, so converting is easy. Use:
return std_logic_vector(to_unsigned(integer(mult*real(2**16)),16));
And, incidentally, you write I thought real * integer was supported. Well, you were wrong. It is not supported. You must use an explicit conversion... except with literals.
Details
Why does it work with literals but not with non-literals? According the VHDL 2008 Language Reference Manual (IEEE Std 1076-2008) universal_integer and universal_real are, respectively, the types of integer and floating point literals.
0.603205 is a universal_real.
2**16 is a bit more complex because it is an expression involving integer literals (universal_integers). Section 9.3.6 Type conversions of the LRM explains that universal_integer can be automatically converted to another integer type when needed by the context. I do not know exactly what automatic conversions take place because the LRM is not very easy to follow on this, but I suspect that the 16 is automatically converted to an integer according rules of section 9.3.6, which allows the use of the universal_integer ** integer operand of package STANDARD that returns a universal_integer (see section 16.3 Package STANDARD where all these operators are defined in their functional form).
Finally, section 9.5 Universal expressions explicitly states that the * operator is defined on universal_integer * universal_real and universal_real * universal_integer. Both return a universal_real.
Note: there is also a universal_real / universal_integer returning universal_real but no universal_integer / universal_real.
Is it possible in any version of VHDL, maybe in 2008, to use a type before it's declared?
E.g. I have this array declaration in the architecture of an entity:
type my_array is array (integer range <>) of my_type;
And still in the same architecture section, but later in the file I have this:
type my_type is record
my_field: signed(31 downto 0);
end record;
Now this gives the following error in Vivado:
[Synth 8-1031] my_type is not declared
The solution is of course to move the record declaration above the array declaration. However this gets very complicated and messy with the number of types increasing (since you basically have to topologically sort your types taking their dependencies into account).
Something like this is supported in every major programming language so I figured maybe it would also exist in VHDL. I also vaguely remember reading about this having been added to VHDL 2008 but can't find any resources about it and my quick test with VHDL 2008 was negative.
So is it possible to use a type in VHDL before its declared, given that the type is declared still in the same architecture, same file, but a few lines below?
Is it possible in any version of VHDL, maybe in 2008, to use a type before it's declared?
No.
IEEE Std 1076-2008 6. Declarations
6.1 General
The language defines several kinds of named entities that are declared explicitly or implicitly by declarations. Each entity’s name is defined by the declaration, either as an identifier or as an operator symbol or a character literal.
...
For each form of declaration, the language rules define a certain region of text called the scope of the declaration (see 12.2). ...
12.2 Scope of declarations
The scope of a declaration, except for an architecture body, extends from the beginning of the declaration to the end of the immediately closing declarative region; the scope of an architecture body extends from the beginning to the end of the architecture body. In either case, this part of the scope of a declaration is called the immediate scope.
12.3 Visibility
A declaration is visible only within a certain part of its scope; this part starts at the end of the declaration except in the declaration of a design unit other than a PSL verification unit, a package declaration, or a protected type declaration, in which case it starts immediately after the reserved word is occurring after the identifier of the design unit, a package declaration, or protected type declaration. This rule applies to both explicit and implicit declarations.
It's the visibility rules stopping you from referencing a type before it's declared.
Also VHDL does not support forward declaration of types other than interface type declarations (generic types), but does for subtypes as your example my_array shows.
The generic types Brian indicates usefulness is limited lacking synthesis vendor support as well as limitations on operations of a type (see 6.5.3 Interface type declarations) summarized in Peter Ashenden's book VHDL 2008 Just the New Stuff:
1.1 Generic Types
VHDL-2008 defines a number of rules covering formal generic types and the ways they can be used. The formal generic type name can potentially represent any constrained type, except a file type or a protected type. The entity can only assume that operations available for all such types are applicable, namely: assignment; allocation using new; type qualification and type conversion; and equality and inequality operations. The formal generic type cannot be used as the type of a file element or an attribute. Moreover, it can only be used as the type of an explicitly declared constant or a signal (including a port) if the actual type is not an access type and does not contain a subelement of an access type. For signals, the predefined equality operator of the actual type is used for driver update and event detection.
Note that for access types there is a special case, where an incomplete type declaration can be references, in order to allow types for linked lists, like:
type value_cell; -- Incomplete declaration
type value_ptr is access value_cell; -- value_cell only for access type
type value_cell is record -- Full declaration
value : bit_vector(0 to 3);
next_cell : value_ptr;
end record value_cell;
However, this is not using the type before declared, and the access type is neither synthesizable, but it is a useful technique for test bench code.
Well, I try to understand limitations in Const expressions in VBScript. I was not able to use anything except literals. What the docs say is:
Literal or other constant, or any combination that includes all
arithmetic or logical operators except Is.
So, if "that includes all arithmetic or logical operators" then logically I expect I can do something like this:
Const X = (1 + 2)
But that brings the error "Expected literal constant". I found an interesting answer here that allows one to cheat, at some level, so the above can be done with:
Execute "Const X = " & (1 + 2)
But my question is about standard constant declaration. If by chance the docs said something like "expression could be ONLY literal", then I would never ask.
So what Else I can use (besides literal)?
Script56.chm says the following in the Remarks section:
Constants are public by default. Within procedures, constants are always private; their visibility can't be changed. Within a script, the default visibility of a script-level constant can be changed using the Private keyword.
To combine several constant declarations on the same line, separate each constant assignment with a comma. When constant declarations are combined in this way, the Public or Private keyword, if used, applies to all of them.
You can't use variables, user-defined functions, or intrinsic VBScript functions (such as Chr) in constant declarations. By definition, they can't be constants. You also can't create a constant from any expression that involves an operator, that is, only simple constants are allowed. Constants declared in a Sub or Function procedure are local to that procedure. A constant declared outside a procedure is defined throughout the script in which it is declared. You can use constants anywhere you can use an expression.
The bit in italics above makes a nonsense of the "or any combination that includes all arithmetic or logical operators except Is" claim.