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