digiwleeaLearn
Open the lab →

Wires, registers, and vectors

wire vs reg, and multi-bit buses

8 min read

In Verilog a wire carries a continuously-driven value while a reg holds a value assigned inside a procedural block; either can be a single bit or a vector such as [7:0] for a multi-bit bus, and every bit is four-state (0, 1, x for unknown, z for high-impedance).

So far the signals in your Verilog have been single bits. Real designs move buses and need to know *what kind* of thing each name is. Verilog has two: a wire (driven continuously, like the output of an assign) and a reg (assigned inside a procedural block, like the always block of a flip-flop). Getting these right, and writing multi-bit values, is most of day-to-day Verilog.

wire versus reg

A wire is exactly what it sounds like: a connection driven by something else, an assign, a module output, a gate. You cannot assign a wire inside an always block. A reg is a name that *holds* its value between assignments and is written inside procedural blocks (always, initial). The rule of thumb: if a signal is driven by assign or a port connection, it is a wire; if it is assigned inside an always block, it must be a reg.
The single most misleading name in Verilog: a **reg is not necessarily a register**. A reg assigned in always @(posedge clk) becomes a flip-flop, but a reg assigned in always @(*) (combinational) is just a wire with a procedural style, no memory at all. reg only means "assigned procedurally," not "stored in hardware." What makes it a real register is a clock edge, not the keyword.

Vectors: a bus in one name

A vector is a multi-bit signal, written with a range: wire [7:0] bus; is an 8-bit bus. bus[7] is the most-significant bit, bus[0] the least; bus[3:0] is the low nibble, a slice. This is how you carry a whole byte on one name instead of eight separate wires, exactly the A[7:0] notation from the hardware. An 8-bit register is just the flip-flop widened to a vector:
module reg8(
  input            clk,
  input      [7:0] D,
  output reg [7:0] Q
);
  always @(posedge clk)
    Q <= D;
endmodule
An 8-bit register: the same posedge-clocked pattern as a one-bit flip-flop, but D and Q are now [7:0] vectors, so all eight bits are captured together on the clock edge. This is the register you built as REG8, in Verilog.
Number literals name a width and base: 8'hFF is eight bits of hex FF (all ones), 8'b1010_0101 is a binary byte (underscores are just for readability), and 4'd5 is four bits of decimal 5. The width before the quote keeps you honest about how many wires a value really occupies.

Parametric modules: one width, any size

You wrote reg8 at exactly eight bits. Tomorrow you need the same thing at 5 bits, or 16, or 32. Copy-pasting the module and hand-editing every [7:0] is how off-by-one bugs get in. A parameter is a named constant you set when you *instantiate* a module, so one source describes a whole family of widths. Declare it in the module header with #(...) and use it in the port ranges, exactly the 8-bit adder you built, but width-generic:
module adder #(parameter WIDTH = 8) (
  input  [WIDTH-1:0] A,
  input  [WIDTH-1:0] B,
  output [WIDTH:0]   sum
);
  assign sum = A + B;
endmodule
A width-generic adder. WIDTH defaults to 8, but every range is written in terms of it, so the same source becomes a 5-bit or 32-bit adder just by changing one number at instantiation. The sum is WIDTH+1 bits wide to hold the carry-out.
To use it, override the parameter with #(...) before the instance name. The one adder source becomes both a 5-bit and an 8-bit adder in the same design:
// 5-bit adder: A, B are [4:0], sum is [5:0]
adder #(.WIDTH(5)) small (.A(a5), .B(b5), .sum(s5));

// 8-bit adder from the SAME module: A, B are [7:0], sum is [8:0]
adder #(.WIDTH(8)) big   (.A(a8), .B(b8), .sum(s8));
Two instances of one module at two widths. #(.WIDTH(5)) makes A and B [4:0] and sum [5:0]; #(.WIDTH(8)) makes A and B [7:0] and sum [8:0]. Leaving the override off would use the declared default of 8. You can also write it positionally as #(5), but naming the parameter is clearer.
A parameter is an elaboration-time constant, fixed the moment the design is built, one value per instance. It is not a signal that changes as the clock ticks. So a parameter can size a bus or count loop iterations, but you cannot drive it from logic at run time. Think of it as filling in a blank on the blueprint before the part is manufactured, not flipping a switch on the running board.

Arrays: many vectors, a memory

A vector [7:0] is a row of 8 bits. Stack many such rows and you get an array, Verilog's model of a memory. Write the element width first (the packed range, [7:0], before the name) and the number of elements after the name (the unpacked range, [0:255]):
reg [7:0] mem [0:255];   // 256 locations, each one byte

mem[0]   = 8'h00;        // whole byte at address 0
mem[255] = 8'hFF;        // whole byte at address 255
data     = mem[addr];    // read the byte selected at runtime by addr
mem is 256 separate 8-bit registers. mem[addr] selects one of them, and addr can be a live signal, so the location is chosen at run time. This is exactly the storage inside the RAM you built.
The two ranges do different jobs: the packed [7:0] says each element is 8 bits wide, and the unpacked [0:255] says there are 256 of them. To reach a single bit you index both: mem[addr][0] is the least-significant bit of the byte at addr. Putting it together, a byte-addressable RAM is one clocked write plus one continuous read:
module ram256(
  input            clk,
  input            we,      // write enable
  input      [7:0] addr,
  input      [7:0] din,
  output     [7:0] dout
);
  reg [7:0] mem [0:255];    // 256 bytes of storage
  always @(posedge clk)
    if (we) mem[addr] <= din;
  assign dout = mem[addr];
endmodule
The RAM from the course in Verilog: 256 bytes, written on a clock edge when we is high, read continuously through dout. mem[addr] on the left of <= writes that location; on the right of assign it reads it.
You can only touch an array one element (or slice) at a time: mem[addr] is a value, but mem alone is not, so you cannot assign or wire up a whole memory by name. And an unpacked array is not a wide vector: reg [7:0] mem [0:255] is 256 addressable bytes (you reach one with mem[addr]), while reg [2047:0] flat is one 2048-bit number (you reach a byte only by slicing, flat[8*addr +: 8]). Same bit count, completely different thing.

Four-state values

Like the signals on your canvas, each Verilog bit is four-state: 0 and 1 (driven low and high), x (unknown, the simulator does not know), and z (high-impedance, driven by nothing, a floating or released tri-state line). An uninitialised reg starts as x; a released bus reads z. Seeing x or z in a simulation is the same debugging signal as X or Z on the canvas: something is uninitialised, contended, or floating.
Why this matters: wire/reg and vectors are the vocabulary for every real Verilog module. A datapath is buses (wire [7:0]) between blocks; a register or the accumulator of your CPU is a reg [7:0] clocked on an edge; and x/z in simulation are how you catch an uninitialised or floating signal before it reaches silicon. Master these and you can read production HDL.
Try it
You write always @(*) y = a & b; where y is combinational (no clock). Should y be declared wire or reg, and does it create a stored register?

Frequently asked

What is the difference between wire and reg in Verilog?

A wire is driven continuously by something else (an assign, a gate, a module output) and cannot be assigned inside a procedural block. A reg holds its value and is assigned inside always or initial blocks. Rule of thumb: assign-driven signals are wires; signals assigned in an always block must be regs.

Does reg in Verilog mean a hardware register?

No, that is the classic trap. A reg is just a signal assigned procedurally. A reg in always @(posedge clk) becomes a flip-flop, but a reg in always @(*) is plain combinational logic with no memory. Only a clock edge makes real storage; the keyword does not.

What is a vector in Verilog?

A vector is a multi-bit signal declared with a range, like wire [7:0] bus for an 8-bit bus. bus[7] is the top bit, bus[0] the bottom, and bus[3:0] is a slice. It lets one name carry a whole byte instead of eight separate wires.

What are the four states of a Verilog signal?

0 (driven low), 1 (driven high), x (unknown, the simulator cannot tell), and z (high-impedance, driven by nothing). These match the 0/1/X/Z states of a wire in hardware: x usually means uninitialised or contended, z means floating or a released tri-state line.

What is a parameter in Verilog?

A parameter is a named constant set when a module is instantiated, so one source can describe many sizes. You declare it in the module header, for example module adder #(parameter WIDTH = 8), use it in the port ranges like input [WIDTH-1:0] A, and override it per instance with adder #(.WIDTH(5)). It is fixed when the design is built, not a signal that changes over time.

How do you declare a memory or array in Verilog?

Put the element width before the name and the number of elements after it: reg [7:0] mem [0:255] is 256 bytes. The packed range [7:0] is each element's bit width, the unpacked range [0:255] is how many elements there are. Index it with mem[addr] to read or write one location at run time, and mem[addr][0] to reach a single bit.

Every lesson here builds toward one thing: a working CPU, from the transistor up.

Open the free lab →
Builds towardTestbenches
Found this useful? Share itShare on X