Skip to content

Verilog · Chapter 5.2.4 · Data & Variables

Scalar vs Vector vs Arrays in Verilog

Every Verilog variable has a dimension, which is one bit, multiple bits packed together, or multiple instances of a packed value. That choice helps determine what hardware the synthesis tool produces. A scalar reg is one flop, a vector reg is a row of flops, and an array of regs is many rows of flops or an inferred SRAM macro when the count is large. The same vocabulary applies to nets such as wire, since every net type supports the same scalar, vector, and array forms with the same rules. This page closes the section by walking through each form, how to access individual bits or groups of bits, the indexed part-selects added in Verilog-2001, the memory-declaration form for register files and SRAMs, and the per-bit and per-entry distinctions that catch beginners.

Foundation22 min readVerilogScalarVectorArrayMemoryBit-Select

Chapter 5 · Section 5.2.4 · Data & Variables

1. The Engineering Problem

A junior engineer needs to declare a 32-bit data path that carries one of four 8-bit values, selected by a 2-bit selector. They write:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg sel0, sel1;         // 2-bit selector — scalar form
reg val00, val01, val02, val03, val04, val05, val06, val07;  // first byte
reg val10, val11, val12, val13, val14, val15, val16, val17;  // second byte
// ... 32 individual signals ...

The code compiles, but the engineer has invented their own naming convention, scattered the signals across 32 declarations, and now every operation on the data path requires 32 individual assignments. A simple mux becomes 256 lines of code; a 4-input adder becomes pages.

The same design with proper dimension usage:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg [1:0]   sel;                  // 2-bit selector — vector form
reg [7:0]   data [0:3];           // 4-entry × 8-bit memory — array form
reg [7:0]   mux_out;              // 8-bit mux output — vector
 
// The mux:
always @(*) mux_out = data[sel];   // 1 line, not 256

sel is now a 2-bit vector (one declaration); data is a 4-entry 8-bit memory (one declaration); the mux is one line of array-indexed access. The synthesis tool produces the same gates as the scalar-form version, but the code is readable, maintainable, and concise.

This page is about picking the right dimension for each signal — scalar when one bit is needed, vector for multi-bit single values, array for multiple instances of a value. Get the dimension right and the rest of the design writes itself; get it wrong and you spend weeks managing the consequences.

2. The Three Dimensions

Verilog supports exactly three dimensions for any variable or net:

DimensionSyntaxBitsUse case
Scalarreg q;1 bitSingle-bit flags, enables, valid signals
Vectorreg [N-1:0] q;N bits, packedMulti-bit values: counters, addresses, data paths
Arrayreg [N-1:0] q [0:M-1];N×M bitsMultiple entries: register files, SRAMs, lookup tables

The same syntax applies to nets (wire, tri, etc.) — wire [7:0] data; declares an 8-bit vector wire; wire [7:0] bus [0:15]; declares a 16-entry × 8-bit array of wires (rare but legal).

The dimension determines the variable's shape in the simulator's memory and the hardware the synthesis tool produces:

  • Scalar → 1 flop (or 1 piece of combinational logic).
  • Vector → N flops (or N pieces of combinational logic, all in parallel).
  • Array → N×M flops (or an inferred SRAM macro for large M).

3. Mental Model

The mental-model has three practical consequences:

  • Range ordering convention is [MSB:LSB] for vectors. Convention is [N-1:0] (descending) — bit N-1 is the most significant, bit 0 is the least. Reversing to [0:N-1] (ascending) is legal but produces byte-order bugs when modules disagree.
  • Range ordering convention is [0:M-1] for arrays. Convention is [0:M-1] (ascending) — entry 0 is the first, entry M-1 is the last. Reversing to [M-1:0] is legal but produces "address goes the wrong way" bugs.
  • You cannot slice across array entries. mem[3:5] is illegal — Verilog requires accessing one entry at a time. To touch three entries, you write three statements (or a loop).

4. Scalar — the 1-bit Form

The simplest dimension. One bit, no brackets, no surprises.

scalar-examples.v — scalar declarations
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
reg        valid;             // single-bit valid flag
reg        ready;             // single-bit ready flag
reg        en;                // enable
reg        rst_n;             // active-low reset
 
wire       gated_clk;         // single-bit clock signal
wire       irq;               // interrupt signal

Scalar regs and wires are the most-used type in any RTL design — every enable, every valid signal, every flag, every single-bit clock-domain signal is scalar. The synthesis tool produces one flop per scalar reg in a clocked always; one wire's worth of metal per scalar wire driven by an assign.

The arithmetic operators on scalars produce single-bit results:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
wire a, b, y, z;
assign y = a & b;        // single-bit AND
assign z = a | b;        // single-bit OR

Comparisons between scalars produce single-bit results. Concatenations of scalars produce vectors: {a, b, c} is a 3-bit vector with a as the MSB.

5. Vector — the Packed Multi-bit Form

The workhorse dimension. Multiple bits packed into a single variable with a contiguous range.

vector-examples.v — vector declarations
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
reg [7:0]   count;            // 8-bit unsigned counter
reg [31:0]  addr;             // 32-bit address
reg [11:0]  data;             // 12-bit data path
reg [N-1:0] param_vec;        // parametric width
 
wire [15:0] result;           // 16-bit combinational output
 
// Less-common ascending range
reg [0:7]   byte_a;           // 8 bits, bit 0 MSB, bit 7 LSB

The range [MSB:LSB] declares which bit is the most significant. The convention [N-1:0] (descending) is universal across every working RTL house — it matches the standard mathematical convention that "the index is the bit weight." A reg [7:0] count; has bit 7 representing 2^7 = 128 and bit 0 representing 2^0 = 1.

5.1 Bit-select

Accessing a single bit:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg [7:0] data = 8'b1010_0110;
 
data[0]       // = 1'b0 (LSB)
data[3]       // = 1'b0
data[7]       // = 1'b1 (MSB)
data[8]       // = 1'bx (out of range — Verilog returns X for out-of-range bit-select)

Variable bit-select:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg [7:0]  data;
reg [2:0]  idx;        // 0 to 7
reg        bit_value;
 
assign bit_value = data[idx];    // dynamic bit select

The dynamic index idx is computed at simulation time. If idx is out of range, the result is X. The synthesis tool generates a mux: bit_value = data[7] when idx=7, data[6] when idx=6, etc.

5.2 Part-select (fixed range)

Accessing a contiguous range of bits:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg [31:0] data = 32'h1234_5678;
 
data[7:0]         // = 8'h78 (lower byte)
data[15:8]        // = 8'h56 (next byte)
data[31:24]       // = 8'h12 (upper byte)
data[31:0]        // = 32'h1234_5678 (the entire value)
data[3:0]         // = 4'h8 (lower nibble)
data[23:0]        // = 24'h34_5678

The selected range must be literal constantdata[3:0] is legal, but data[idx+3:idx] is not (this is what the indexed part-selects in §5.3 are for).

5.3 Indexed part-select (Verilog-2001)

Verilog-2001 added two operators for accessing variable-base ranges:

  • base+:width — base is the LSB of the slice, width is the number of bits.
  • base-:width — base is the MSB of the slice, width is the number of bits.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg [31:0] data = 32'h1234_5678;
reg [3:0]  word_idx;
 
// Fixed-width 8-bit slice with variable base
data[0  +: 8]      // = data[7:0]   = 8'h78
data[8  +: 8]      // = data[15:8]  = 8'h56
data[16 +: 8]      // = data[23:16] = 8'h34
data[word_idx*8 +: 8]   // dynamic byte select
 
// Or the -: form for "MSB first":
data[31 -: 8]      // = data[31:24] = 8'h12
data[23 -: 8]      // = data[23:16] = 8'h34

The width must be a constant; the base can be a variable expression. This is the canonical way to do byte-by-byte access of a wide word — data[byte_idx*8 +: 8] extracts the byte_idx-th byte regardless of the value of byte_idx at simulation time.

dynamic-byte-access.v — typical use of +:
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module byte_extractor (
    input  wire [31:0] data,
    input  wire [1:0]  byte_idx,    // 0..3
    output wire [7:0]  byte_out
);
    assign byte_out = data[byte_idx*8 +: 8];
endmodule

Synthesis output: a 4-input mux on the 8-bit width, with byte_idx as the select. The dynamic byte access is the canonical use of +:.

6. Array — the Multi-entry Form

The third dimension. Multiple vector entries, each addressable by a separate index.

array-examples.v — array (memory) declarations
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
reg [7:0]   mem      [0:255];      // 256-entry × 8-bit byte memory
reg [31:0]  rf       [0:31];       // 32-entry × 32-bit register file
reg [15:0]  table_q  [0:1023];     // 1K-entry × 16-bit lookup table
 
// Parametric memory
parameter DEPTH = 1024;
parameter WIDTH = 16;
reg [WIDTH-1:0] data_mem [0:DEPTH-1];
 
// Multi-dimensional array (Verilog-2001 supports limited multi-dim)
reg [7:0] cube [0:3][0:3];     // 4×4 grid of 8-bit values

The first dimension [WIDTH-1:0] (before the name) is the word width — bits per entry. The second dimension [0:DEPTH-1] (after the name) is the depth — number of entries.

6.1 Array indexing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg [7:0] mem [0:255];
 
mem[0]               // 8-bit value at address 0
mem[100]             // 8-bit value at address 100
mem[addr]            // dynamic addressing
mem[addr][3:0]       // lower nibble of the entry at addr
mem[addr][bit_idx]   // single bit at (addr, bit_idx)

The two-step indexing — first the entry, then a bit or part-select within the entry — is the canonical pattern for "memory at address X, bit Y."

6.2 What you cannot do with arrays

Three operations are illegal on Verilog arrays (Verilog-2001):

  • Slicing across entries. mem[3:5] is illegal — you cannot get a sub-range of entries as a single value.
  • Assigning the entire array as a single literal. mem = '{default:0}; is SystemVerilog, not Verilog. In Verilog you must initialise entry-by-entry via a generate loop, an initial block with explicit assignments, or $readmemh() / $readmemb() to load from a file.
  • Passing the entire array as a port. Verilog-2001 does not allow output reg [7:0] mem [0:255] in a port list. To export memory from a module, expose individual entries via dedicated ports, or use a memory-export side channel (a debug/backdoor interface).

The SystemVerilog extensions (covered in the SystemVerilog track) lift these restrictions; for pure Verilog-2001, the restrictions are real.

6.3 Initialisation

Arrays have no implicit initialisation — every entry is X on every bit at t = 0. Three ways to set them up:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// (1) In a clocked always block (synthesisable reset path)
integer i;
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        for (i = 0; i < 256; i = i + 1) mem[i] <= 8'h00;
    end else if (wr_en) begin
        mem[addr] <= wr_data;
    end
end
 
// (2) In an initial block (testbench-only or large memories)
integer i;
initial begin
    for (i = 0; i < 256; i = i + 1) mem[i] = 8'h00;
end
 
// (3) From a file (testbench-only — synthesisable in some tools)
initial $readmemh("mem_init.hex", mem);    // hex file
initial $readmemb("mem_init.bin", mem);    // binary file

The $readmemh() form is the canonical way to initialise large memories in testbenches; the file format is one entry per line, in hex ($readmemh) or binary ($readmemb). Some FPGA flows (Xilinx Vivado, Intel Quartus) accept $readmemh() in synthesisable RTL — the tool reads the file at synthesis time and embeds the values as initial flop states or BRAM init values.

7. Visual Explanation

Three figures cover the dimension hierarchy.

7.1 Visual A — the three shapes

A side-by-side picture of the three dimensions on the same logical 8-bit data.

Three dimensions of Verilog variables

data flow
Three dimensions of Verilog variablesscalar1 bit (reg q;)vector8 bits packed (reg [7:0] q;)arrayM × 8 bits (reg [7:0] q [0:M-1];)1 flopsmallest8 flopsall clocked together8×M flopsor inferred SRAM
The three shapes. Scalar is one bit. Vector is N bits, packed into one variable. Array is M entries, each entry an N-bit vector. The synthesis tool produces 1, N, or N×M flops respectively — or for large arrays, infers an SRAM macro.

7.2 Visual B — bit-select and part-select operators

The access syntax for a vector. Four ways to access bits within a 32-bit vector.

Vector access operatorsreg [31:0] data;32-bit vectordata[3]bit-select (1 bit)data[15:8]fixed part-select (8 bits)data[base +: 8]indexed +: (variable base)data[base -: 8]indexed -: (variable base)12
Four access patterns on a 32-bit vector. Bit-select picks one bit. Fixed part-select picks a contiguous range with literal bounds. Indexed part-select +: picks a width-N slice starting at a variable base. Indexed part-select -: picks a width-N slice ending at a variable base. The +: and -: forms (Verilog-2001) are essential for dynamic byte / word access in wide buses.

7.3 Visual C — array structure

The structural view of a memory: M entries side by side, each an N-bit vector. Address selects the entry; bit-select within the entry picks a bit.

Memory structurereg [7:0] mem[0:M-1];M entries × 8 bitsmem[0]8-bit entrymem[1]8-bit entry...M-2 entriesmem[M-1]8-bit entry12
Memory structure: M entries × N bits each. The address selects one of the M entries; the bit-select / part-select inside that entry picks a sub-range. For a typical RISC register file (32 × 32-bit), the synthesis tool produces 1024 flops + a 32-way mux for the read port. For larger memories (4 KB or more), SRAM macros are inferred.

8. The Concatenation and Replication Operators

A vector can be built from smaller pieces using concatenation { , } and replication {N{ }}. These are essential for byte-packing and width-conversion.

8.1 Concatenation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg [3:0]  nibble_high = 4'hA;
reg [3:0]  nibble_low  = 4'h5;
reg [7:0]  byte_val;
 
byte_val = {nibble_high, nibble_low};    // = 8'hA5
 
// More general
reg [31:0] word;
word = {byte_val, 24'h00_00_00};         // = 32'hA5_00_00_00
 
// Mix scalars and vectors
reg sign_bit = 1'b1;
reg [6:0] mantissa = 7'b1010101;
reg [7:0] composite = {sign_bit, mantissa};    // = 8'b1101_0101

Concatenation builds a vector left-to-right: the first argument is the MSB. The total width is the sum of the argument widths.

8.2 Replication

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg [3:0]  pattern = 4'h5;
reg [15:0] replicated;
 
replicated = {4{pattern}};       // = 16'h5555 (= 4 copies of 4'h5)
 
// Common pattern: sign-extension
reg [7:0]  signed_byte = 8'h80;   // = -128 in two's complement
reg [31:0] sign_extended;
sign_extended = {{24{signed_byte[7]}}, signed_byte};   // = 32'hFFFF_FF80
 
// Common pattern: zero-padding
reg [15:0] zero_padded;
zero_padded = {8'h00, signed_byte};                     // = 16'h0080

Replication is the canonical way to sign-extend or zero-extend a narrow value into a wider one. The {{24{signed_byte[7]}}, signed_byte} idiom is standard for "sign-extend an 8-bit signed value to 32 bits."

8.3 The reverse: distributing a vector

There's no direct "reverse concatenation" operator. To split a vector into pieces, use part-selects:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg [31:0] word = 32'hAABB_CCDD;
reg [7:0]  byte0 = word[7:0];     // = 8'hDD
reg [7:0]  byte1 = word[15:8];    // = 8'hCC
reg [7:0]  byte2 = word[23:16];   // = 8'hBB
reg [7:0]  byte3 = word[31:24];   // = 8'hAA

The pattern is straightforward — one part-select per piece. For an array of bytes, a generate loop or for loop assigns each byte from a different part-select of the source word.

9. Multi-dimensional Arrays

Verilog-2001 added limited support for multi-dimensional arrays — declarations with more than one post-name range.

multi-dim-array.v — 2D array syntax
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
reg [7:0] cube [0:3][0:3];           // 4×4 grid of 8-bit values
reg [7:0] tensor [0:3][0:3][0:3];    // 4×4×4 cube of 8-bit values
 
// Access
reg [7:0] x;
x = cube[2][1];                       // entry at row 2, col 1
x = tensor[1][2][3];                  // entry at [1][2][3]

Multi-dimensional arrays produce the same hardware as a flat memory with computed index: cube[i][j] is functionally equivalent to mem[i*4 + j]. The multi-dim syntax is just notational sugar.

The same restrictions from §6.2 apply — no slicing across the higher dimensions, no array-as-port, no array-as-literal.

For purely-Verilog-2001 codebases, multi-dim arrays are uncommon — most engineers flatten to a 1D memory with explicit index computation. SystemVerilog (the language extension) has stronger support for multi-dim arrays and removes most of the Verilog-2001 restrictions.

10. Simulation Behavior

The simulator stores vectors and arrays as packed bit arrays in memory:

  • Scalar reg — 1 byte of simulator memory (overhead, since the actual bit value fits in much less).
  • Vector reg [N-1:0]ceil(N / 32) * 4 bytes (rounded to 32-bit words).
  • Array reg [N-1:0] mem [0:M-1]M * ceil(N / 32) * 4 bytes.

For a 32 × 32-bit register file: 32 × 32 / 8 = 128 bytes of simulator memory. For a 1 KB byte memory: 1024 bytes (8 KB of memory if N=8 and the simulator stores X / Z bits separately).

Bit-selects and part-selects are computed at simulator runtime — the simulator extracts the relevant bits from the packed representation when the expression is evaluated. Dynamic bit-selects (data[idx]) produce a mux at synthesis time; the simulator emulates the mux with conditional branches.

11. Synthesis Behavior

The synthesis tool maps the three dimensions differently:

  • Scalar reg → 1 flop (clocked) or 1 piece of combinational logic (always @(*) with full coverage).
  • Vector reg [N-1:0] → N flops in parallel (clocked) or N parallel pieces of combinational logic.
  • Array reg [N-1:0] mem [0:M-1] → for small M, N×M discrete flops + a multi-way mux for the read port. For large M, an inferred SRAM macro.

The threshold between "discrete flops" and "inferred SRAM" depends on the tool, the technology library, and the access pattern. Synopsys Design Compiler typically infers SRAM for memories above 256 entries with a synchronous read pattern; smaller memories or asynchronous-read patterns map to discrete flops. Xilinx Vivado infers BlockRAM for memories above 2 KB on most FPGAs.

synthesis-rules.v — memory inference patterns
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
// Pattern 1: synchronous write, asynchronous read → register file (small) or SRAM (large)
reg [7:0] mem [0:1023];
always @(posedge clk) if (wr_en) mem[wr_addr] <= wr_data;
assign rd_data = mem[rd_addr];    // async read
 
// Pattern 2: synchronous read → almost always SRAM
reg [7:0] mem [0:1023];
reg [7:0] rd_data_q;
always @(posedge clk) begin
    if (wr_en) mem[wr_addr] <= wr_data;
    rd_data_q <= mem[rd_addr];    // sync read
end

Pattern 2 (synchronous read) is the canonical SRAM-friendly pattern. Most FPGA BRAMs and ASIC SRAM macros have one cycle of read latency; the reg [7:0] rd_data_q; matches that latency naturally.

For a working RTL engineer, the rule is: declare memory with the access pattern that matches the desired implementation. Async-read patterns produce register files (small, fast); sync-read patterns produce SRAM (large, one-cycle latency). The synthesis tool's inference is deterministic for these canonical patterns.

12. Waveform Analysis

A waveform showing dynamic bit-select and part-select behaviour on a 16-bit vector.

Dynamic bit-select and part-select on a 16-bit vector

12 cycles
Dynamic bit-select and part-select on a 16-bit vectordata=0x1234, bit_idx=0 → bit_sel=0 (LSB of 0x1234)data=0x1234, bit_idx=0…data=0xABCD, bit_idx=4 → bit_sel=0 (bit 4 of 0xABCD)data=0xABCD, bit_idx=4…data=0xBEEF, bit_idx=15 → bit_sel=1 (MSB)data=0xBEEF, bit_idx=1…clkdata12341234ABCDABCD567856785678DEADDEADBEEFBEEFBEEFbit_idx1212151515bit_selbyte_sel3434BCBC565656DEDEBEBEBEt0t1t2t3t4t5t6t7t8t9t10t11
bit_sel = data[bit_idx] reads the bit at the dynamic index. byte_sel = data[bit_idx +: 8] reads an 8-bit slice starting at the dynamic base. The +: form is essential for variable-base part-selects — it cannot be replaced by a simple [a:b] expression because a and b must be constants in fixed part-selects.

The waveform illustrates the difference between bit-select (single bit, dynamic index legal) and indexed part-select (multi-bit slice, dynamic base via +:). Both are legal expressions; the bit-select produces a 1-bit result; the part-select produces a fixed-width slice.

13. Industry Use Cases

Three patterns that account for the vast majority of dimension choices in production RTL.

13.1 Scalar for control signals

Every RTL block has dozens of single-bit control signals: enables, valids, readys, flags, status bits, error indicators. These are all scalar. Naming convention: descriptive names (wr_en, valid_q, error_flag) rather than abbreviations.

13.2 Vector for data paths

Every RTL block has multi-bit data paths: counters, addresses, data values, packet headers, register contents. These are all vectors with explicit width. Naming convention: descriptive names with width suffix when ambiguity exists (addr_q, data_in, count_q). The explicit width is part of the design contract.

13.3 Array for memories

Register files (small), SRAMs (large), lookup tables (any size), packet buffers, FIFO storage, decoder tables. All arrays. Naming convention: descriptive names with optional _mem or _arr suffix (reg_file, cache_mem, coeff_tbl).

The dimension choice is the design decision. The synthesis tool produces the gates that match.

14. Common Mistakes

Five pitfalls that catch engineers using dimensions.

14.1 Reversed vector range

Writing reg [0:7] data; (ascending) when convention is reg [7:0] data; (descending). The reversed range works inside the module but causes byte-order bugs when the module's vector is concatenated with vectors from other modules using the convention. Always use [N-1:0] for vectors.

14.2 Reversed array range

Writing reg [7:0] mem [255:0]; (descending) when convention is reg [7:0] mem [0:255]; (ascending). The reversed range works but produces "address goes backwards" confusion. Always use [0:M-1] for arrays.

14.3 Slicing across array entries

mem[3:5] is illegal. Cannot get a sub-range of entries as a single value. Use a loop or multiple statements.

14.4 Forgetting the array initialisation

Arrays start at X on every bit at t = 0. Reading from an array before writing produces X-propagation. Reset every entry in synthesisable RTL (via a generate loop), or use $readmemh() for initial values from a file.

14.5 Width mismatch on bit-select assignment

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg [7:0] data;
reg       bit_val;
bit_val = data[idx];    // bit_val is 1-bit, data[idx] is 1-bit — match
                         // BUT: bit_val = data; → 7-bit truncation warning
 
reg [3:0] nibble;
nibble = data[7:0];     // 8-bit slice assigned to 4-bit reg — truncation warning

Width-mismatch warnings are common. The fix is explicit slicing or explicit width conversion.

15. Debugging Lab

Three dimension-related debug post-mortems

16. Coding Guidelines

  1. Use [N-1:0] for vectors. Descending range. Convention across every working RTL house. Bit N-1 is the MSB.
  2. Use [0:M-1] for arrays. Ascending range. Entry 0 is the first; entry M-1 is the last.
  3. reg for procedural-assignment vectors; wire for continuous-assignment vectors. The 5.2 type-family rule.
  4. Use +: for byte/word access with LSB-anchored base. word[byte_idx*8 +: 8]. Use -: only when the base is the MSB of the slice.
  5. Always reset every entry of small arrays (register files). Generate loop in the reset path.
  6. Use $readmemh() for large memory initialisation. Cleaner than 1000 lines of explicit assignments; works in some synthesis tools for FPGA flows.
  7. No slicing across array entries. Use loops. Verilog-2001 limitation; SystemVerilog removes it.
  8. Use concatenation { , } for byte-packing. Replication {N{ }} for sign-extension and zero-padding.

17. Interview Q&A

18. Exercises

Three exercises that turn dimension choices into reflex.

Exercise 1 — Pick the dimension

For each signal description, recommend scalar, vector, or array.

#Signal description
1A "valid" flag indicating output data is ready
2A 12-bit address bus
3A 16-entry × 32-bit register file
4An "enable" signal for a counter
5A 64-bit packet header
6A 256-entry × 8-bit lookup table

Hints. Single-bit flags / enables → scalar. Multi-bit single values → vector. Multiple entries → array.

Exercise 2 — Dynamic byte extraction

Write a Verilog module extract_byte that takes a 32-bit word and a 2-bit byte index, and returns the selected 8-bit byte. Specifications:

  • Inputs: wire [31:0] word, wire [1:0] byte_idx.
  • Output: wire [7:0] byte_out.
  • Behaviour: byte_idx=0 → byte_out = word[7:0]; byte_idx=1 → word[15:8]; etc.

Hints. Use the +: indexed part-select: word[byte_idx*8 +: 8]. The expression is one line.

Exercise 3 — Spot the dimension bugs

The following module has three dimension-related bugs. Identify and fix each.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module buggy (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [0:31] data_in,            // bug #1
    input  wire [1:0]  byte_idx,
    output reg  [7:0]  byte_out,
    output reg         err
);
    reg [7:0] history [7:0];                // bug #2
 
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            byte_out <= 8'h00;
            err      <= 1'b0;
            // bug #3: history not reset
        end
        else begin
            byte_out <= data_in[7:0];       // also broken due to bug #1
            err      <= |history[3:0];      // bug #4
        end
    end
endmodule

What to produce. (a) Identify each bug. (b) Show the fix for each. (c) Explain why the broken version compiled but produced wrong results.

19. Summary

Verilog supports three dimensions for any variable or net:

  • Scalar — 1 bit. Single-bit flags, enables, valids. reg q;.
  • Vector — N bits, packed. Multi-bit values. reg [N-1:0] q;.
  • Array — M entries × N bits. Register files, SRAMs, lookup tables. reg [N-1:0] mem [0:M-1];.

The access operators on vectors:

  • Bit-selectdata[i] returns the i-th bit (1 bit wide).
  • Fixed part-selectdata[7:0] returns a literal-bounded slice.
  • Indexed part-select (+:)data[base +: width] returns a width-bit slice starting at the variable base (LSB-anchored).
  • Indexed part-select (-:)data[base -: width] returns a width-bit slice ending at the variable base (MSB-anchored).

The access operators on arrays:

  • Array indexmem[i] returns the i-th entry (N bits wide).
  • Bit-select within an entrymem[i][j] returns bit j of entry i.
  • Part-select within an entrymem[i][7:0] returns the lower 8 bits of entry i.

The conventions:

  • Vector range: [N-1:0] (descending). Bit N-1 is MSB.
  • Array range: [0:M-1] (ascending). Entry 0 is first.

The hardware mapping:

  • Scalar → 1 flop.
  • Vector → N flops in parallel.
  • Array → N×M flops (small) or inferred SRAM (large).

The day-to-day discipline:

  • Pick the dimension that matches the signal's role. Flags / enables → scalar. Multi-bit values → vector. Multiple entries → array.
  • Always use [N-1:0] for vectors, [0:M-1] for arrays. No exceptions.
  • Use +: for byte / word access with LSB-anchored base. word[byte_idx*8 +: 8].
  • No slicing across array entries. Use loops or multiple statements.
  • Reset every array entry in synthesisable code. Generate loop in the reset path.

Section 5.2 closes here. The four sub-pages — reg-type, integer-type, real-type, and this one — cover every variable in Verilog. Chapter 5 closes; Chapter 6 picks up with constant variables (parameter, localparam) — the keywords that turn hard-coded values into design-time parameters.

  • real — Chapter 5.2.3; the floating-point variable that shares the variable family.
  • integer — Chapter 5.2.2; the 32-bit signed sibling.
  • reg — Chapter 5.2.1; the workhorse variable type that all dimensions extend.
  • Register Data Types — Chapter 5.2; the parent overview.
  • Variables & Data Types — Chapter 5; nets and variables together.