Overview

Arrays are the vector representation of the fundamental datatypes. Arrays can be homogeneous or non-homogeneous . They can also be packed or unpacked . The fundamental difference between packed and unpacked is; that they occupy space in computer differently. Packed is treated as a single continous space of memory, accessed all at the same time, while unpacked is treated as a group of continous segments of memory.

Some common examples of array declarations are given below:-

reg [1:0] A
reg [4] A
reg [0:1] A
wire [3:0] B
reg [1:0][3:0] B
reg C [1:0]
reg [3:0] C [1:0]
wire [3:0][7:0] C [2][2]
reg [3:0] D [1:0][1:0][1:0]
integer E [1:0]
real F [16]

Packed Arrays

Packed arrays refer to the array declaration where the entirity of the array is treated as a single variable. Indices can still be access individually, but the entire array can also be directly used. Examples of the usage are given below :-

reg [31:0] A
A[1] = 1'b0
A = 32'd100
A[3:2] = 2'd3
$display( "A : %d", A )

Unpacked Arrays

Unpacked arrays refer to the array declaration where the array consists of homogenous elements, ie elements of the same datatype. Elements can be accessed using indices, but the entire array cannot be directly used unlike packed arrays. Examples of the usage are given below :-

integer A [3:0]
A[0] = 32'b0
A[1] = 32'd100
A[3:2] = '{ 32'd100, 32'd200 }
$display( "A : %p", A )
$display( "A[1] : %d", A[1] )

Vector Slicing

Bit-wise part selection is a feature in Verilog that lets you select between a range of bits from an even larger vector. It's an inherited feature in SystemVerilog, so all the rules are the same. It is sometimes also just referred to as " part select ".

To explain it simply; you can use an expression to periodically select a range of bits from a given vector. An expression could mean that you might want to pick 4 bits at a time in an incrementing order from a vector. Or you might want to pick 1 byte at a time in a decrementing order. Either way, this operator is extremely useful in reducing your code length and in some cases, it also improves the readability of your code. There are two kinds of part selects; namely constant and indexed.

Constant Part Select: Here, the expressions inside the address range used in the vector are expressions that ultimately evaluate constants. One additional point is that, if the index is out of bounds, then the output is 'x .

reg [31:0] X;

initial
begin
   // vector[ msb_expr : lsb_expr ]
   X[2**3+1-2:(2**0)+1] = 1; 
end

Indexed Part Select: In indexed part select, the final range is decided concerning a variable expression for the index and the constant range following it.

reg [31:0] BIG;
reg [0:31] LITTLE;
integer i; // Used as Index

initial
begin
    BIG = $urandom;
    LITTLE = $urandom;

    $display( "BIG : %h", BIG );
    $display( "LITTLE : %h", LITTLE );

    for( i = 0; i < 32/8; i = i + 1 )
    begin
        // big_endian_vector[ lsb_expr +: constant_width ]
        // The ranges selected will be [7:0], [15:8], [23:16], [31:24] 
        // based on the value of i ( 0 to 3 )
        // BIG[ 8*i +: 8 ];
        $display( "BIG[ 8*%0d +: 8 ] : %h", BIG[ 8*i +: 8 ] );
        // little_endian_vector[ msb_expr +: constant_width ]
        // The ranges selected will be [0:7], [8:15], [16:23], [24:31] 
        // based on the value of i ( 0 to 3 )
        // LITTLE[ 8*i +: 8 ];
        $display( "LITTLE[ 8*%0d +: 8 ] : %h", LITTLE[ 8*i +: 8 ] );
        // big_endian_vector[ msb_expr -: constant_width ]
        // The ranges selected will be [31:24], [23:16], [15:8], [7:0]
        // based on the value of i ( 3 to 0 )
        // BIG[ 8*i -: 8 ];
        $display( "BIG[ 8*%0d -: 8 ] : %h", BIG[ 8*i -: 8 ] );
        // little_endian_vector[ lsb_expr -: constant_width ]
        // The ranges selected will be [24:31], [16:23], [8:15], [0:7]
        // based on the value of i ( 3 to 0 )
        // LITTLE[ 8*i -: 8 ];
        $display( "LITTLE[ 8*%0d -: 8 ] : %h", LITTLE[ 8*i -: 8 ] );
    end
end

Working Examples: ( You may simulate the following code to cross check the results )

reg [31:0] BIG;
reg [0:31] LITTLE;
integer i; // Used as Index

function get_index_in_8( input integer num );
    get_index_in_8 = num*8;
endfunction

initial
begin   
   for( i = 2; i < 4; i = i + 1 )
   begin
       $display( "BIG[ %0d : %0d ] = %0d", (8*(i+1))-1, (8*(i+1))-8, BIG[ 8*i +: 8 ] );
   end
   for( i = 2; i < 4; i = i + 1 )
   begin
       $display( "LITTLE[ %0d : %0d ] = %0d", (8*(i+1))-8, (8*(i+1))-1, LITTLE[ 8*i +: 8 ] );
   end

   for( i = 3; i >= 0; i = i - 1 )
   begin
       $display( "BIG[ %0d : %0d ] = %0d", (8*(i+1))-1, (8*(i+1))-8, BIG[ 8*i-1 -: 8 ] );
   end
   for( i = 3; i >= 0; i = i - 1 )
   begin
       $display( "LITTLE[ %0d : %0d ] = %0d", (8*(i+1))-8, (8*(i+1))-1, LITTLE[ 8*i-1 -: 8 ] );
   end

   $display( "BIG[32] = %0d", BIG[32] );
   $display( "BIG[39:32] = %0d", BIG[2*8+:8] );
   $display( "LITTLE[32] = %0d", LITTLE[32] );
   $display( "LITTLE[39:32] = %0d", LITTLE[2*8+:8] );
   
   // Using a function call to get the index expression
   $display( "BIG[2*8+:8] = %0d", BIG[get_index_in_8(1)+:8] );

end 
Theory Questions
    The maximum bit width that can be declared in Verilog's packed array is tool-dependent, but typically, it can go up to 2^31-1 bits (around 2 billion bits) in most simulators. However, practical limits may vary based on simulator or synthesis tool constraints.
    Theoratically, no. But simulators will have some limitations to limit the number of dimensions.
    Yes. Bu default for example, reg [8] A translates to reg [7:0] A .
    module top;
        integer A[4];
    
        initial
        begin
            A = 10;
        end
    endmodule
    
    No, this type of assignment is invalid, since its not possible to assing a integral value to an array directly.
Coding Questions
    module top;
        integer ARR[2][2][2][2];
        integer i[4];
    
        initial
        begin
            for( i[0] = 0; i[0] < 2; i[0] += 1 )
            begin
                for( i[1] = 0; i[1] < 2; i[1] += 1 )
                begin
                    for( i[2] = 0; i[2] < 2; i[2] += 1 )
                    begin
                        for( i[3] = 0; i[3] < 2; i[3] += 1 )
                        begin
                            ARR[i[0]][i[1]][i[2]][i[3]] = $random;
                        end
                    end
                end
            end
        end
    endmodule
    
    module top;
      
      reg [17:0] NUM;
      
      initial
      begin
        NUM = {18{1'b1}};
        
        $display( "%d", NUM );
      end
      
    endmodule
    
    module top;
      
      reg [63:0] NUM_PACKED;
      reg [3:0] NUM_UNPACKED [16];
      
      initial
      begin
        NUM_PACKED = {$urandom,$urandom};
        
        $display( "%h", NUM_PACKED );
        
        // Conversion to Unpacked
        
        {>>{NUM_UNPACKED}} = NUM_PACKED;
        
        $display( "%p", NUM_UNPACKED );
      end
      
    endmodule
    
    module top;
      
      integer NUM;
      
      initial
      begin
        NUM = {$urandom};
        
        $display( "%h", NUM );
        
        // Printing Index Slices
            
        $display( "%d", NUM[3] );
        $display( "%h", NUM[3:0] );
      end
      
    endmodule
    
    module top;
      
      integer NUM;
      
      initial
      begin
        NUM = 0;
        
        $display( "%h", NUM );
        
        // Printing The Varibale Aftre assigning x to one bit
            
        NUM[7] = 1'bx;
        $display( "%0h", NUM ); // When printing with %h, inly the slice which has an X will be X, while when printing with %0h, the entire value will be printed as X.
        $display( "%h", NUM );
        $display( "%d", NUM );
        $display( "%0d", NUM );
      end
      
    endmodule
    
    module top;
      
      integer NUM;
      
      initial
      begin
        NUM = $urandom;
        
        $display( "%b", NUM );
        
        // Flipping the bits using steaming operator
        NUM = {<<{NUM}};
        
        $display( "%b", NUM );
      end
      
    endmodule