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 named0, 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 + 1works,data == 32'h0works,data[7:0]works. - An unpacked array is many signals. You cannot treat the whole thing as a number:
mem + 1is 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.