Menu

Verilog Vectors and Arrays: Multi-Bit Signals Explained

How to declare multi-bit signals with [7:0], slice them, combine them, and the difference between a packed vector and a memory array.

This page includes runnable editors - edit, run, and see output instantly.

One Wire vs Many

So far every signal we've seen has been one bit wide. Real designs almost never look like that - addresses are 16 or 32 bits, data buses are 8 or 64, RGB pixels are 24. Verilog gives you a single mechanism to make any signal multi-bit: add a range in square brackets.

wire [7:0] data;       // 8-bit wire, bit 7 is MSB, bit 0 is LSB
reg  [15:0] address;   // 16-bit reg
output reg [31:0] result; // 32-bit module output

The numbers between the brackets are the bit positions of the most and least significant bits. [7:0] means "this signal has bits numbered 7 down to 0", which works out to 8 bits total. The first number is the high index. The second is the low.

You'll occasionally see [0:7] instead - same number of bits, opposite endianness for slicing purposes. The [high:low] form is the overwhelming industry convention; stick with it unless you have a strong reason not to.

A Worked Example: An 8-Bit Adder

Each + adds the two 8-bit vectors and produces a 9-bit result. The number literals like 8'd10 say "an 8-bit-wide decimal value of 10" - we'll cover those in Number Literals.

Slicing: Picking Out Bits

Once you have a vector, you can pull out individual bits or contiguous ranges:

Don't get caught up in the testbench's force line - we just need a way to inject a value to demonstrate slicing. The interesting bit is the slices themselves.

A few rules:

  • The slice direction has to match the declaration. If you declared [7:0], you slice with [high:low]. Reversing the direction is a syntax error.
  • Slicing out of range produces x (unknown) in simulation. The synthesis tool may warn or error.
  • Bit selection is zero-based on the index you wrote - data[0] is the bit named 0, which (for a [7:0] declaration) is the LSB.

Variable-Base Slices: +: and -:

A common need: "give me 8 bits starting at bit N". You can't write data[N+7:N] directly because Verilog requires both ends of the range to be constants. The syntax that solves this:

data[base +: width]   // width bits starting at `base`, going UP
data[base -: width]   // width bits starting at `base`, going DOWN

The width is constant (we're picking 8 bits at a time), but the base can be a runtime expression. That's exactly what you need for byte-addressable memories, shift-register taps, and so on.

Arrays: A Step Beyond Vectors

A vector is a single multi-bit signal. An array is a collection of vectors, indexed independently:

reg [31:0] mem [0:1023];

That declaration is two ranges, and the two ranges mean different things:

  • [31:0] is the packed dimension - the width of each individual word.
  • [0:1023] is the unpacked dimension - how many words there are.

So mem is 1024 separate 32-bit registers. You access one with a single index:

mem[5] = 32'hCAFE_BABE;       // write word 5
data   = mem[address];        // read the word at `address`

That's a tiny memory holding squares. Real designs use the same pattern to hold register files, lookup tables, FIFOs, and any other on-chip storage that's bigger than a single vector.

Packed vs Unpacked: Why It Matters

The split between packed and unpacked dimensions shows up everywhere. Knowing which is which saves a lot of debugging:

  • A packed vector is one signal. You can treat the whole thing as a number: data + 1 works, data == 32'h0 works, data[7:0] works.
  • An unpacked array is many signals. You cannot treat the whole thing as a number: mem + 1 is a syntax error. You have to pick out a specific word first.

Multiple packed dimensions are also legal:

reg [3:0][7:0] regs;   // 4 bytes packed together into a 32-bit signal

regs[0] is a byte (the low byte). regs as a whole is 32 bits. SystemVerilog uses this heavily.

Multiple unpacked dimensions create a 2D memory:

reg [31:0] frame [0:479][0:639];  // 480x640 of 32-bit pixels

You access a single pixel with frame[y][x]. This is how an image buffer would look in HDL.

What Comes Next

You can now declare and manipulate any-width signal you need. The next doc - Parameters - shows how to make those widths configurable so the same module works at 8 bits in one instance and 32 in another. Then we'll move on to the rules for writing literal numbers (8'b1010_1100, 32'hDEAD_BEEF), and the x/z values that show up whenever something isn't driven.

Frequently Asked Questions

What is a vector in Verilog?

A vector is a multi-bit signal. You declare one by adding a range to a wire or reg: wire [7:0] data is an 8-bit wire. The numbers between the brackets are the bit positions - in this case bit 7 is the most significant and bit 0 is the least. You can slice individual bits (data[3]) or contiguous ranges (data[7:4]).

What does [7:0] mean in Verilog?

[7:0] declares a range from bit 7 down to bit 0, inclusive - an 8-bit signal with bit 7 as the most significant bit. The first number is the high index, the second is the low. You can also write [0:7] for little-endian indexing, but the [high:low] form is by far the more common convention in industry code.

How do you slice bits in Verilog?

Use bracket indexing. data[3] picks a single bit. data[7:4] picks the top four bits as a 4-bit vector. The slice has to be in the same direction as the declaration - if you declared [7:0], slice with [high:low]. SystemVerilog also adds data[3 +: 4] for variable-base slices of constant width.

What is the difference between a packed and unpacked array in Verilog?

A packed array is a single contiguous bus - reg [31:0] word is one 32-bit signal. An unpacked array (or 'memory') is a collection of independent words - reg [31:0] mem [0:1023] is 1024 separate 32-bit registers. You can read or write a whole word of an unpacked array but you can't operate on the whole thing as one signal.

How do you declare a memory in Verilog?

reg [31:0] mem [0:1023]; declares a memory of 1024 entries, each 32 bits wide. The first set of brackets is the word width (packed); the second is the number of words (unpacked). Access an entry with mem[address], and you can read or write a slice of that entry with mem[address][7:0] once SystemVerilog-2005 indexing is enabled.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED