Featured post

Top 5 books to refer for a VHDL beginner

VHDL (VHSIC-HDL, Very High-Speed Integrated Circuit Hardware Description Language) is a hardware description language used in electronic des...

Monday, 31 December 2012

SystemVerilog Interface

Interfaces are a major new construct in SystemVerilog, created specifically to encapsulate the communication between blocks, allowing a smooth refinement from abstract system-level through successive steps down to lower RTL and structural levels of the design. Interfaces also facilitate design re-use. Interfaces are hierarchical structures that can contain other interfaces.

There are several advantages when using an Interface:

  • They encapsulate connectivity: an interface can be passed as a single item through a port, thus replacing a group of names by a single one. This reduces the amount of code needed to model port connections and improves its maintainability as well as readability.
  • They encapsulate functionality, isolated from the modules that are connected via the interface. So, the level of abstraction and the granularity of the communication protocol can be refined totally independent of the modules.
  • They can contain parameters, constants, variables, functions and tasks, processes and continuous assignments, useful for both system-level modelling and testbench applications.
  • They can help build applications such as functional coverage recording and reporting, protocol checking and assertions.
  • They can be used for port-less access: An interface can be instantiated directly as a static data object within a module. So, the methods used to access internal state information about the interface may be called from different points in the design to share information.
  • Flexibility: An interface may be parameterised in the same way as a module. Also, a module header can be created with an unspecified interface instantiation, called a Generic Interface. This interface can be specified later on, when the module is instantiated.

At its simplest, an interface is a named bundle of wires, similar to a struct, except that an interface is allowed as a module port, while a struct is not.

// Interface definition
interface Bus;
  logic [7:0] Addr, Data;
  logic RWn;
endinterface


// Using the interface
module TestRAM;
  Bus TheBus();                   // Instance the interface
  logic[7:0] mem[0:7];
  RAM TheRAM (.MemBus(TheBus));   // Connect it

  initial
  begin
    TheBus.RWn = 0;               // Drive and monitor the bus
    TheBus.Addr = 0;
    for (int I=0; I<7; I++)
      TheBus.Addr = TheBus.Addr + 1;
    TheBus.RWn = 1;
    TheBus.Data  = mem[0];
  end
endmodule


module RAM (Bus MemBus);
  logic [7:0] mem[0:255];

  always @*
    if (MemBus.RWn)
      MemBus.Data = mem[MemBus.Addr];
    else
      mem[MemBus.Addr] = MemBus.Data;
endmodule


Interface Ports

An interface can also have input, output or inout ports. Only the variables or nets declared in the port list of an interface can be connected externally by name or position when the interface is instantiated, and therefore can be shared with other interfaces.  The ports are declared using the ANSI-style.

Here is an example showing an interface with a clock port:

interface ClockedBus (input Clk);
  logic[7:0] Addr, Data;
  logic RWn;
endinterface
module RAM (ClockedBus Bus);
  always @(posedge Bus.Clk)
    if (Bus.RWn)
      Bus.Data = mem[Bus.Addr];
    else
      mem[Bus.Addr] = Bus.Data;
endmodule


// Using the interface
module Top;
  reg Clock;

  // Instance the interface with an input, using named connection
  ClockedBus TheBus (.Clk(Clock));
  RAM TheRAM (.Bus(TheBus));
  ...
endmodule

Parameterised Interface

This is a simple example showing a parameterised interface:

interface Channel #(parameter N = 0)
    (input bit Clock, bit Ack, bit Sig);
  bit Buff[N-1:0];
  initial
    for (int i = 0; i < N; i++)
      Buff[i] = 0;
  always @ (posedge Clock)       
   if(Ack = 1)
     Sig = Buff[N-1];
   else
     Sig = 0;
endinterface

Tasks in Interfaces

Tasks and functions can be defined in interfaces, to allow a more abstract level of modelling.

The next example shows two tasks in an interface being used to model bus functionality. The tasks are called inside the testRAM module:

interface MSBus (input Clk);
  logic [7:0] Addr, Data;
  logic RWn;

  task MasterWrite (input logic [7:0] waddr,
                    input logic [7:0] wdata);
    Addr = waddr;
    Data = wdata;
    RWn = 0;
    #10ns RWn = 1;
    Data = 'z;
  endtask
  task MasterRead (input  logic [7:0] raddr,
                   output logic [7:0] rdata);
    Addr = raddr;
    RWn = 1;
    #10ns rdata = Data;
  endtask
endinterface
module TestRAM;
  logic Clk;
  logic [7:0] data;
  MSBus TheBus(.Clk(Clk));
  RAM TheRAM (.MemBus(TheBus));
  initial
  begin
    // Write to the RAM
    for (int i = 0; i<256; i++)
      TheBus.MasterWrite(i[7:0],i[7:0]);

    // Read from the RAM
    for (int i = 0; i<256; i++)
    begin
      TheBus.MasterRead(i[7:0],data);
      ReadCheck : assert (data === i[7:0])
        else $error("memory read error");
    end
  end
endmodule

Access Type

Formal Definition

A type that provides access to an object of a given type. Access to such an object is achieved by an access value returned by an allocator; the access value is said to designate the object.

Simplified Syntax

access subtype_indication
type identifier;

Description

Access type allows to manipulate data, which are created dynamically during simulation and which exact size is not known in advance. Any reference to them is performed via allocators, which work in a similar way as pointers in programming languages.
The subtype_indication in the access type declaration denotes the type of an object designated by a value of an access type. It can be any scalar, composite or other access type (example 1). File type is not allowed here.
The only objects allowed to be of the access type are variables.
The default value of an access type is null which designates no object at all. To assign any other value to an object of an access type an allocator has to be used (see allocator for details).
The access type allows to create recursive data structures (dynamic lists of objects created during simulation) which consist of the records that contain elements of access types - either the same or different than the actually declared. In order to handle declarations of such recursive data structures so called incomplete type declaration is needed which plays a role of an "announcement" of a type which will be declared later on.
For each incomplete type declaration there must be a corresponding full type declaration with the same name. The complete declaration must appear in the same declarative part. A type declared as incomplete may not be used for any other purposes than to define an access type before the complete type definition is accomplished. Such an incomplete type declaration is presented in example 2 below.

Examples

Example 1
-- declaration of record type Person:
type Person is record
            address:ADRESS_TYPE;
            age:DATE;
end record Person;
-- declaration of access type Person_Access:
type Person_Access is access Person;
The Person_Access type defines a pointer (dynamic link) to a record declared earlier and called Person.
Example 2
-- declaration of an incomplete type Queue_Element:
type Queue_Element;
-- declaration of an access type Queue_Element_Ptr:
type Queue_Element_Ptr is access Queue_Element;
-- declaration of an full record type Queue_Element:
type Queue_Element is record
                  name:STRING(1 to 20);
                  address:ADRESS_TYPE;
                  age:DATE;
                  succ:Queue_Element_Ptr;
end record Queue_Element;
The type Queue_Element contains the Queue_Element_Ptr field, which in turns points to Queue_Element. In order to declare both types sequentially, first the Queue_Element is declared in an incomplete way, which allows declaring the access type Queue_Element_Ptr. Finally, complete declaration of Queue_Element must be provided.

Important Notes

· Application of access types is restricted to variables: only variables can be of an access value. Also, only variables can be designated by an access value.
· Although access types are very useful for modeling potentially large structures, like memories or FIFOs, they are not supported by synthesis tools.













Aggregate

Formal Definition

A basic operation that combines one or more values into a composite value of a record or array type.

Syntax:

aggregate ::= ( element_association { , element association } ) element_association ::= [choices => ] expression choices ::= choice { | choice } choice ::= simple expression          | discrete_range          | element_simple_name          | others

Description

The aggregate assigns one or more values to the elements of a record or array creating the composite value of this type. Aggregates are composed of element associations, which associate expressions to elements (one expression per one or more elements). Element associations are specified in parentheses and are separated by commas.

An expression assigned to an element or elements must be of the same type as the element(s).

Elements can be referred to either by textual order they have in the object declaration (so called positional associations - example 1) or by its name (named associations - example 2). Both methods can be used in the same aggregate, but in such a case all positional associations appear first (in textual order) and all named associations appearing next (in any order). In any case if the association others is used, it must be the last one in an aggregate.

The choice clause, denoting selection of element(s) can have any of the following forms: simple expression, discrete range, simple name or reserved word others.

A value of simple expression can be applied in arrays only and must belong to discrete range of an array type. A simple expression specifies the element at the corresponding index value. Example 2 illustrates this concept.

A discrete range must meet the same conditions as a simple expression. It is useful when several consecutive elements of an array are assigned the same value (example 3). A discrete range serves for defining the set of indexes only and the direction specified or implied has no significance.

The use of element simple name as a choice is restricted to records only. In this case, each element is identified by its name (Example 4).

When some elements are assigned different values and the remaining elements will receive some other value, reserved word others can be used to denote those elements (Example 5). Such a choice must be the last in an aggregate and can be used both in arrays and in records, provided that the remaining elements of the records are of the same type.

The choice others can serve as a very convenient way to assign the same value to all elements of some array, e.g. to reset a wide bus (Example 6).

If several elements are assigned the same value, a multiple choice can be used. In such a case a bar sign (|) separates references to elements (Example 7). If a multiple choice is used in an array aggregate, it may not be mixed with positional associations.

Examples

Example 1

variable Data_1 : BIT_VECTOR (0 to 3) := ('0','1','0','1');

Bits number 0 and 2 are assigned the value '0', while bits 1 and 3 are assigned '1'. All element associations here are positional.

Example 2

variable Data_2 : BIT_VECTOR (0 to 3) := (1=>'1',0=>'0',3=>'1',2=>'0');

Like in the previous example, bits number 0 and 2 are assigned the value '0', while bits 1 and 3 are assigned '1'. The element associations here, however, are named. Note that in this case the elements can be listed in arbitrary order.

Example 3

signal Data_Bus : Std_Logic_Vector (15 downto 0);
. . .
Data_Bus <= (15 downto 8 => '0', 7 downto 0 => '1');

Data_Bus will be assigned the value of "0000000011111111". The first element is associated a value in positional way (thus it is bit number 15), and the other two groups are assigned values using discrete ranges.

Example 4

type Status_Record is record
     Code : Integer;
     Name : String (1 to 4);
end record;
variable Status_Var : Status_Record := (Code => 57, Name => "MOVE");

Choice as an element simple name can be used in record aggregates - each element is associated a value (of the same type as the element itself).

Example 5

signal Data_Bus : Std_Logic_Vector (15 downto 0);
. . .
Data_Bus <= (14 downto 8 => '0', others => '1');

Data_Bus will be assigned the same value as in example 3 ("1000000011111111"), but this aggregate is written in more compact way. Apart from bits 14 through 8, which receive value '0' all the others (15 and 7 through 0) will be assigned '1'. Note that the choice others is the last in the aggregate.

Example 6

signal Data_Bus : Std_Logic_Vector (15 downto 0);
. . .
Data_Bus <= (others => 'Z');

Instead of assigning "ZZZZZZZZZZZZZZZZ" to Data_Bus in order to put it in high impedance state, an aggregate with the others choice representing all the elements can be used.

Example 7

signal Data_Bus : Std_Logic_Vector (15 downto 0);
. . .
Data_Bus <= (15 | 7 downto 0 => '1',
others => '0');

Note the multiple choice specification of the assignment to the bits 15 and 7 through 0. The result of the assignment to Data_Bus will be the same as in examples 3 and 5 ("1000000011111111").

Important Notes

· Associations with elements' simple names are allowed in record aggregates only.

· Associations with simple expressions or discrete ranges as choices are allowed only in array aggregates.

· Each element of the value defined by an aggregate must be represented once and only once in the aggregate.

· Aggregates containing the single element association must always be specified using named association in order to distinguish them from parenthesized expressions.

· The others choice can be only the last in an aggregate.

Alias

Formal Definition

An alternate name for an existing named entity.

Simplified Syntax

alias alias_name : alias_type is object_name;

Description

The alias declares an alternative name for any existing object: signal, variable, constant or file. It can also be used for "non-objects": virtually everything, which was previously declared, except for labels, loop parameters, and generate parameters.

Alias does not define a new object. It is just a specific name assigned to some existing object.

Aliases are prevalently used to assign specific names to slices of vectors in order to improve readability of the specification (see example 1). When an alias denotes a slice of an object and no subtype indication is given then the subtype of the object is viewed as if it was of the subtype specified by the slice.

If the alias refers to some other object than a slice and no subtype indication is supported then the object is viewed in the same way as it was declared.

When a subtype indication is supported then the object is viewed as if it were of the subtype specified. In case of arrays, the subtype indication can be of opposite direction than the original object (example 2).

Subtype indication is allowed only for object alias declarations.

A reference to an alias is implicitly a reference to the object denoted by the alias (example 3).

If an alias denotes a subprogram (including an operator) or enumeration literal then a signature (matching the parameter and result type) is required (example 4). See signature for details.

Examples

Example 1

signal Instruction : Bit_Vector(15 downto 0);
alias OpCode : Bit_Vector(3 downto 0) is Instruction(15 downto 12);
alias Source : Bit_Vector(1 downto 0) is Instruction(11 downto 10);
alias Destin : Bit_Vector(1 downto 0) is Instruction(9 downto 8);
alias ImmDat : Bit_Vector(7 downto 0) is Instruction(7 downto 0);

The four aliases in the example above denote four elements of an instruction: operation code, source code, destination code and immediate data supported for some operations. Note that in all declarations the number of bits in the subtype indication and the subtype of the original object match.

Example 2

signal DataBus : Bit_Vector(31 downto 0);
alias FirstNibble : Bit_Vector(0 to 3) is DataBus(31 downto 28);

DataBus and FirstNibble have opposite directions. A reference to FirstNibble(0 to 1) is equivalent to a reference to DataBus(31 downto 30).

Example 3

signal Instruction : Bit_Vector(15 downto 0);
alias OpCode : Bit_Vector(3 downto 0) is Instruction(15 downto 12);
. . .
if Opcode = "0101" -- equivalent to if Instruction(15 downto 12) = "0101"
then
    . . .

Both conditions are exactly the same, but the one where alias is used is more readable.

Important Notes

· VHDL Language Reference Manual uses the name 'entity' to denote a language unit, i.e. object, parameter etc. It is completely different idea than a design entity.

· Many synthesis tools do not support aliases.

Allocator

Formal Definition

An operation used to create anonymous, variable objects accessible by means of access values.

Simplified Syntax

new subtype_indication

new qualified_expression

Description

Each time an allocator is evaluated, a new object is created and the object is designated (pointed) by an access value (pointer). The type of the object created by an allocator is defined either by a subtype indication (example 1 and 2) or a qualified expression (example 3 and 4).

In case of allocators with a subtype indication, the initial value of the created object is the same as the default initial value of a directly declared variable of the same subtype (example 1 and 2). When qualified expression is used, the initial value is defined by the expression itself (example 3 and 4).

If an allocator creates an object of the array type, then the array must be constrained. This can be achieved through using a constrained subtype or specified in the subtype indication with an explicit index constraint (example 2).

Copying a value of a variable with allocated object to other variable does not create new object. Instead, both variables point to the same object (example 5).

See also access type.

Examples

Example 1

type Table is array (1 to 8) of Natural;
type TableAccess is access Table;
variable y : TableAccess;
...
y := new Table; -- will be initialized with
-- (0, 0, 0, 0, 0, 0, 0, 0)

The allocator (note that the allocator is of the access type) creates a new object of the Table type, which is initialized to a default value, equal in this case to (0, 0, 0, 0, 0, 0, 0, 0).

Example 2

z:= new BIT_VECTOR(1 to 3);

This allocator creates a new object of the BIT_VECTOR type, consisting of three elements. The default initial value of this object is equal to ('0','0','0'). Note that the subtype indication is constrained as the BIT_VECTOR type is unconstrained.

Example 3

type test_record is record
            test_time : time;
            test_value : Bit_Vector (0 to 3);
end record test_record;
type AccTR is access test_record;
variable x,z : AccTR;
x := new test_record'(30 ns, B"1100"); -- record allocation with aggregate
z := new test_record;
z.test_time := 30 ns;
z.test_value := B"1100";

Initial values can be assigned to an object (in this case a record) created by an allocator both using a qualified expression (in this case with an aggregate - allocator for x) or using a subtype indication and later on direct assignments (allocator for z). In both cases above the objects created will be identical (although it will not be the same object).

Example 4

type AccBV is access Bit_Vector(7 downto 0);
variable Ptr1, Ptr2 : AccBV;
Ptr1 := new Bit_Vector(7 downto 0);
Ptr2 := Ptr1;

There is no allocator assigned to Ptr2, thus no new object will be created for it. Instead it will point to the same object, which was created for Ptr1.

Important Notes

· For each access type an implicitly declared procedure Deallocate is defined. The procedure reverses the evaluation of an allocator, i.e. releases the storage occupied by an object created by an allocator.

· Allocators (and access types) are not synthesizable.

· A subtype indication in allocator must not include a resolution function.

· An object created by an allocator has not its own name (indicator). Instead, it is referred to through the name, which it was allocated to.

· The concept of access types and allocators is very much the same as the concept of pointers in software programming languages.

Architecture

Formal Definition

A body associated with an entity declaration to describe the internal organization or operation of a design entity. An architecture body is used to describe the behavior, data flow, or structure of a design entity.

Simplified Syntax

architecture architecture_name of entity_name is

  architecture_declarations

begin

  concurrent_statements

end [ architecture ] [ architecture_name ];

Description

Architecture assigned to an entity describes internal relationship between input and output ports of the entity. It consists of two parts: declarations and concurrent statements.

First (declarative) part of an architecture may contain declarations of types, signals, constants, subprograms (functions and procedures), components, and groups. See respective topics for details.

Concurrent statements in the architecture body define the relationship between inputs and outputs. This relationship can be specified using different types of statements: concurrent signal assignment, process statement, component instantiation, concurrent procedure call, generate statement, concurrent assertion statement and block statement. It can be written in different styles: structural, dataflow, behavioral (functional) or mixed.

The description of a structural body is based on component instantiation and generate statements. It allows to create hierarchical projects, from simple gates to very complex components, describing entire subsystems. The connections among components are realized through ports. Example 1 illustrates this concept for a BCD decoder.

The Dataflow description is built with concurrent signal assignment statements. Each of the statements can be activated when any of its input signals changes its value. While these statements describe the behavior of the circuit, a lot of information about its structure can be extracted form the description as well. Example 2 contains this type of description for the same BCD decoder as in the previous example.

The architecture body describes only the expected functionality (behavior) of the circuit, without any direct indication as to the hardware implementation. Such description consists only of one or more processes, each of which contains sequential statements (Example 3).

The architecture body may contain statements that define both behavior and structure of the circuit at the same time. Such architecture description is called mixed (Example 4).

Examples

Example 1

architecture Structure of Decoder_bcd is
signal S: Bit_Vector(0 to 1);
component AND_Gate
  port(A,B:in Bit; D:out Bit);
end component;
component Inverter
port(A:in Bit; B:out Bit);
end component;
begin
Inv1:Inverter port map(A=>bcd(0), B=>S(0));
Inv2:Inverter port map(A=>bcd(1), B=>S(1));
A1:AND_Gate port map(A=>bcd(0), B=>bcd(1), D=>led(3));
A2:AND_Gate port map(A=>bcd(0), B=>S(1), D=>led(2));
A3:AND_Gate port map(A=>S(0), B=>bcd(1), D=>led(1));
   A4:AND_Gate port map(A=>S(0), B=>S(1), D=>led(0));
end Structure;

The components Inverter and AND_Gate are instantiated under the names Inv1, Inv2, A1, A2, A3 and A4. The connections among the components are realized by the use of signals S(0), S(1) declared in the architecture's declarative part.

Example 2

architecture Dataflow of Decoder_bcd is
begin
   led(3) <= bcd(0) and bcd(1);
   led(2) <= bcd(0) and (not bcd(1));
   led(1) <= (not bcd(0)) and bcd(1);
   led(0) <= (not bcd(0)) and (not bcd(1));
end Dataflow;

All the four statements here are executed concurrently and each of them is activated individually when any of its input signals changes its value.

Example 3

architecture procedural of Decoder_bcd is
signal S: bit_vector (3 downto 0);
begin
P1: process (bcd, S)
   begin
     case bcd is
         when "00" => S <= "0001" after 5 ns;
         when "01" => S <= "0010" after 5 ns;
         when "10" => S <= "0100" after 5 ns;
         when "11" => S <= "1000" after 5 ns;
     end case;
led <= S;
   end process;
end procedural;

The clause "after 5 ns" here allows to introduce time delay of the circuit. The assignment of a new value to the led signal will be done only after 5 nanoseconds of the simulated time.

Example 4

architecture Mixed of Decoder_bcd is
signal S: Bit_Vector(0 to 2);
component Inverter
port(A: in Bit; B: out Bit);
end component;
begin
Inv1: Inverter port map (A=>bcd(0), B=>S(0));
Inv2: Inverter port map (A=>bcd(1), B=>S(1));
P: process (S, bcd)
begin
      led(0) <= S(0) and S(1) after 5 ns;
      led(1) <= S(0) and bcd(1) after 5 ns;
      led(2) <= bcd(0) and S(1) after 5 ns;
      led(3) <= bcd(0) and bcd(1) after 5 ns;
end process;
end Mixed;

Above, two Inverter component instantiation statements define the circuit responsible for determining the value of the signal S. This signal is read by behavioral part i.e. the process statement P. In this process, the values computed by the and operation are assigned to the led output port.

Important Notes

· Single entity can have several architectures, but architecture cannot be assigned to different entities.

· Architecture may not be used without an entity.

· All declarations defined in an entity are fully visible and accessible within each architecture assigned to this entity.

· Different types of statements (i.e. processes, blocks, concurrent signal assignments, component instantiations, etc.) can be used in the same architecture.

Array

Formal Definition

A type, the value of which consists of elements that are all of the same subtype (and hence, of the same type). Each element is uniquely distinguished by an index (for a one-dimensional array) or by a sequence of indexes (for a multidimensional array). Each index must be a value of a discrete type and must lie in the correct index range.

Simplified Syntax

type type_name is array (range) of element_type

type type_name is array (type range <>) of element_type

Description

The array is a composite object, which elements are of the same subtype. Each of the elements is indexed by one or more indices belonging to specified discrete types. The number of indices is the number of dimensions, i.e. one-dimensional array has one index, two-dimensional has two indices, etc. The order of indices is significant and follows the order of dimensions in the type declaration (example 1).

An array may be either constrained or unconstrained. The array is constrained if the size of the array is constrained. The size of the array can be constrained using a discrete type mark or a range. In both cases, the number of the elements in the array is known during the compilation. Several declarations of constrained arrays are presented in example 1.

The array is said to be unconstrained if its size is unconstrained: the size of the unconstrained array is declared in the form of the name of the discrete type, which range is unconstrained. The number of elements of unconstrained array type is unknown. The size of a particular object is specified only when it is declared. Example 2 presents several declarations of unconstrained arrays.

Package STANDARD contains declarations of two one-dimensional unconstrained predefined array types: STRING and BIT_VECTOR. The elements of the STRING type are of the type CHARACTER and are indexed by positive values (i.e. counted from 1), and the elements of the BIT_VECTOR type are of the type BIT and are indexed by natural values (i.e. counted from 0). See string type and Bit_Vector for details.

Array elements are referenced by indices and can be assigned values individually or using concatenation, aggregates, slices or any mixture of those methods. See respective topics for details.

Examples

Example 1

type Real_Matrix is array (1 to 10) of REAL;
type BYTE is array (0 to 7) of BIT;
type Log_4_Vector is array (POSITIVE range 1 to 8, POSITIVE range 1 to 2) of Log_4;
type X is (LOW, HIGH);
type DATA_BUS is array (0 to 7, X) of BIT;

The type Real_Matrix is an array consisting of 10 elements, each of which is of the type REAL. Log_4_Vector is a two-dimensional array 82 and its elements are of type Log_4 (which must have been declared earlier). Also the type DATA_BUS is a two-dimensional array of the same size, but note that one of the dimensions is defined as enumeration type.

Example 2

-- unconstrained array of element of Real type:
type Real_Matrix is array (POSITIVE range <>) of Real;
variable Real_Matrix_Object : Real_Matrix (1 to 8);
-- unconstrained array of elements of Log_4 type:
type Log_4_Vector is array (NATURAL range <>, POSITIVE range<>) of Log_4;
variable L4_Object : Log_4_Vector (0 to 7, 1 to 2);

Examples of unconstrained types: Real_Matrix is when an unconstrained type and an object of this type is declared (Real_Matrix_Object) it is restricted to 8 elements. In similar way L4_Object is constrained from an unconstrained two-dimensional type Log_4_Vector.

Important Notes

· Synthesis tools do generally not support multidimensional arrays. The only exceptions to this are two-dimensional "vectors of vectors". Some synthesis tools allow two-dimensional arrays.

· Arrays may not be composed of files.