Skip to content

Verilog · Chapter 5.2.1 · Data & Variables

reg in Verilog — Procedural Storage and Synthesis Interpretation

The reg keyword is the single most-used name in synthesisable Verilog. Every flip-flop output, every combinational always block output, every testbench stimulus variable, every state-machine state, and every memory cell is declared as reg. It is the workhorse of the language, and yet it is the most misunderstood. The textbook framing that reg means register is wrong. The rule that actually matters is that what reg becomes after synthesis depends on the always block, not on the keyword. This page is the deep drill, covering width and signedness, blocking versus non-blocking assignment, the four synthesis outcomes of flop, latch, combinational logic, or no hardware, memory declarations, and the canonical patterns that account for almost all reg usage in production RTL.

Foundation24 min readVerilogregProceduralFlopLatchalways

Chapter 5 · Section 5.2.1 · Data & Variables

1. The Engineering Problem

Three engineers each write a 4-bit register meant to hold the output of an adder:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Engineer A
module adder_reg_a (input wire clk, input wire [3:0] a, b, output reg [3:0] sum_q);
    always @(posedge clk) sum_q <= a + b;
endmodule
 
// Engineer B
module adder_reg_b (input wire clk, input wire [3:0] a, b, output reg [3:0] sum_q);
    always @(*) sum_q = a + b;
endmodule
 
// Engineer C
module adder_reg_c (input wire clk, input wire [3:0] a, b, input wire en, output reg [3:0] sum_q);
    always @(*) if (en) sum_q = a + b;
endmodule

All three declare sum_q as reg [3:0]. The keyword is the same. The simulator and synthesis tool see three different designs:

  • Engineer A's module is a 4-bit flip-flop on the adder output — registered output, one cycle of delay, holds value between clock edges.
  • Engineer B's module is a 4-bit combinational adder — no flop, no clock, sum_q follows a + b continuously. Functionally identical to assign sum_q = a + b; on a wire.
  • Engineer C's module is a latch — when en=1, sum_q follows a + b; when en=0, sum_q holds its previous value via a transparent latch. Lint warns; this is almost always a bug.

Same keyword, three different pieces of hardware. The difference is entirely in the always block — its sensitivity list, its assignment operator, and whether every output is assigned in every branch. The reg keyword's only job is to mark "this signal is procedurally assigned"; the always block decides everything else.

This page covers the rules that turn a reg declaration into specific hardware. The §1 example is the topic in miniature: reg is one keyword, and what it becomes depends on the code around it.

2. Anatomy of a reg Declaration

The full declaration syntax for a reg:

reg-syntax.v — every legal form of reg declaration
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
// <reg> [signed] [range] name [= initial_value];
 
reg                       q0;             // scalar, unsigned, default X at t=0
reg        [7:0]          count_q;        // 8-bit vector, unsigned
reg signed [15:0]         s_acc_q;        // 16-bit signed
reg        [WIDTH-1:0]    payload_q;      // parametric width
reg                       toggle_q = 1'b0;// initial value at t=0 (testbench-friendly)
 
// Memory declaration — array of regs
reg        [7:0]          mem [0:255];    // 256 entries × 8 bits
reg signed [15:0]         coeffs [0:31];  // 32 entries × 16-bit signed
 
// Port-list declarations
module m (
    input  wire        clk,
    output reg  [7:0]  count_q,           // reg in port list — driven internally
    output reg         valid_q            // single-bit reg output
);

The four optional modifiers:

  • signed — changes the arithmetic semantics of <, >, >>>, and the width-extension rules. Default is unsigned. Using signed on a counter or address is almost always a mistake (see §10.5 of the parent overview).
  • Range [MSB:LSB] — makes the reg a vector. Convention is [N-1:0] (descending). Mixing [0:N-1] (ascending) across modules causes byte-order bugs.
  • Initial value = value — sets the reg's value at t = 0. Legal in declarations but not synthesisable in most flows — the synthesis tool ignores initial values and the reset path determines the actual silicon initial state. Use only for testbench regs.
  • Memory dimensions [M:N] — turns the reg into an array (a memory). The 5.2.4 sub-page covers the scalar / vector / array dimensions in depth.

2.1 The default initial value is X

Every bit of every reg at simulation start is X (unknown) — the 4-state value meaning "neither 0 nor 1." This is the source of countless "my output is X everywhere" debugging sessions in early simulation. Two prophylactics:

  • In synthesisable RTL, every reg is reset to a defined value during the asynchronous-reset path. Missing reset paths produce X-propagation at gate-level simulation.
  • In testbenches, every reg is initialised in an initial block before any sampling. The initial-value syntax (reg q = 1'b0;) is a convenient way to set the value at declaration; the synthesis tool ignores it, but the simulator honours it.

2.2 4-state values

A reg carries 4-state values per bit — 0, 1, Z, or X. The Z (high-impedance) value is rare in reg (it shows up when a tri-state output is assigned 1'bz from a procedural block) but supported by the simulator. The X (unknown) value is the universal "I don't know" marker. Operators propagate X pessimistically: 1 + X = X, 1 & X = X, but 0 & X = 0 (the 0 masks the unknown).

3. Mental Model

The mental-model has four practical consequences:

  • The always block style is the design decision, not the reg declaration. Choosing always @(posedge clk) vs always @(*) is the design choice; the reg declaration that follows is just bookkeeping.
  • Reset every reg in clocked always. Every flop should reset to a defined value during the asynchronous-reset path. Missing reset paths produce X-propagation at the gate level.
  • Cover every branch in combinational always. Every reg assigned inside always @(*) must be assigned in every branch of every if / case statement. Missing branches produce latches.
  • Don't mix = and <= in the same always block. Clocked blocks use <= (non-blocking, NBA region, sample-then-update semantic). Combinational blocks use = (blocking, Active region, immediate propagation). Mixing produces simulator-dependent behaviour.

4. Blocking vs Non-Blocking Assignment

The IEEE 1364-2005 §9.2.1 (procedural assignments) defines two assignment operators inside procedural blocks. The distinction is the single biggest decision a Verilog beginner has to make correctly.

4.1 Blocking (=) — immediate write

The right-hand side is evaluated and the variable is updated in the same simulator event, in the Active region of the stratified scheduler. Subsequent statements in the same block see the new value immediately.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(*) begin
    x = a + b;         // x updated immediately to a+b
    y = x + c;         // y uses the NEW x (= a+b+c)
end

The blocking semantic models propagation through gates — one stage at a time, with each stage's output feeding the next. This is the right model for combinational logic, where a logical change at the input ripples through every gate in the path on the same time slot.

4.2 Non-blocking (<=) — deferred write

The right-hand side is evaluated immediately (in the Active region), but the actual write to the variable is deferred to the NBA region, which runs strictly after every Active-region event in the same time slot.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) begin
    x <= a + b;        // RHS evaluated now (= a+b); write to x deferred
    y <= x + c;        // RHS uses the OLD x (the pre-assignment value)
end

The non-blocking semantic models the clock edge of a flip-flop bank — every flop reads its D input simultaneously at the clock edge, then every flop updates its Q output together. This is the right model for sequential logic: the sample-then-update behaviour matches what the silicon's edge-triggered flops do.

4.3 The rule

The rule is universal across every working RTL house:

  • Clocked always blocks (@(posedge clk), @(negedge clk)) use <= only. Every assignment is non-blocking.
  • Combinational always blocks (@(*)) use = only. Every assignment is blocking.
  • Don't mix. A clocked always with = produces simulator-dependent behaviour. A combinational always with <= produces simulation timing issues.

The 5.2 overview's §8 (Simulator Scheduling Insight) framed this rule; the working-engineer enforcement is one inviolable rule per always block.

5. The Four Synthesis Outcomes

The synthesis tool reads the always block driving each reg and chooses one of four outcomes.

5.1 Flip-flop (clocked always)

The most common pattern. A clocked always block with <= assignments produces a flip-flop bank — one D flop per bit of the reg.

rtl/reg_flop.v — the workhorse flop pattern
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module flop_example (
    input  wire        clk,
    input  wire        rst_n,
    input  wire        en,
    input  wire [7:0]  d,
    output reg  [7:0]  q
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)      q <= 8'h00;        // asynchronous reset
        else if (en)     q <= d;             // synchronous write
        // else: q holds previous value (clock-enable flop)
    end
endmodule

Synthesis output: an 8-bit D flop bank with asynchronous reset and clock enable. Every modern standard-cell library has this cell — DFFRE or similar — at minimal area and power.

Three discipline rules:

  • Use <= for clocked assignment. Always.
  • Reset every flop unconditionally during !rst_n. Every bit gets a defined value at reset.
  • Either reset or hold. Missing the else branch is fine — the flop holds its previous value when the if/else if chain doesn't match. The synthesis tool infers the enable correctly.

5.2 Combinational wire (always @(*) with full coverage)

A combinational always block with = assignments and every output assigned in every branch synthesises to combinational logic — no flop, no latch, no storage.

rtl/reg_combinational.v — combinational reg, no storage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module mux_example (
    input  wire [1:0]  sel,
    input  wire [7:0]  a, b, c, d,
    output reg  [7:0]  y
);
    always @(*) begin
        case (sel)
            2'b00: y = a;
            2'b01: y = b;
            2'b10: y = c;
            2'b11: y = d;      // every case covered
        endcase
    end
endmodule

Synthesis output: a 4-to-1 mux on the 8-bit data. Same gates as assign y = sel == 2'b00 ? a : sel == 2'b01 ? b : sel == 2'b10 ? c : d; on a wire. The reg form is more readable for case statements; the assign form is more compact for simple ternaries.

The "full coverage" rule:

  • Every reg assigned in the block must be assigned in every case branch. Adding a default to a case (even an empty one with a "don't care" value) is the safest pattern.
  • Or set the default at the top of the block. y = 8'h00; case (sel) ... endcase covers any missing branch with the default.

5.3 Transparent latch (always @(*) with incomplete coverage)

The "almost always a bug" outcome. A combinational always block with = assignments that does not assign every output in every branch produces a latch.

rtl/reg_latch.v — usually unintended
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module latch_example (
    input  wire        en,
    input  wire [7:0]  data,
    output reg  [7:0]  q
);
    always @(*) begin
        if (en) q = data;
        // No else branch — q is not assigned when en=0
        // Synthesis infers a transparent latch to hold the previous value.
    end
endmodule

Synthesis output: an 8-bit transparent latch on the en signal. The reg q follows data when en=1 and holds its previous value when en=0.

A latch is almost always a bug. Reasons:

  • Transparent during the active level of the enable, opaque during the inactive level. Timing analysis is much harder than a flop (no clock edge to anchor the timing).
  • Forbidden by most ASIC sign-off rules. The synthesis tool emits a LATCH inferred warning that should be treated as an error.
  • Easy to write accidentally. Any incomplete if or case in a combinational always produces one.

The fix is to provide complete coverage. Either an explicit else:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(*) begin
    if (en) q = data;
    else    q = 8'h00;        // explicit else with default
end

Or a default at the top:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(*) begin
    q = 8'h00;                 // default
    if (en) q = data;          // override
end

Both forms give the synthesis tool complete coverage and produce combinational logic (a 2-input mux), not a latch.

5.4 No hardware (initial block, testbench-only)

A reg assigned only in an initial block is testbench-only. The initial construct is not synthesisable; the synthesis tool ignores it entirely.

rtl/reg_testbench.v — testbench-only reg, no hardware
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module tb_stimulus;
    reg [7:0]  stim_q;        // stimulus register — testbench-only
 
    initial begin
        stim_q = 8'h00;
        #10 stim_q = 8'h01;
        #10 stim_q = 8'h02;
        #10 stim_q = 8'h04;
        #10 $finish;
    end
endmodule

This produces no hardware — the tb_stimulus module is a testbench harness, not a synthesisable design. The reg stim_q; exists purely in the simulator's memory to drive stimulus signals into the DUT. Testbenches use this pattern extensively for scoreboards, expected-value tracking, configuration variables, and stimulus generators.

6. Visual Explanation

Three figures cover the reg synthesis decision flow.

6.1 Visual A — the always-block decision tree

The synthesis tool's interpretation flow. Read the always block; identify the sensitivity list and the assignment operator; decide the hardware.

Synthesis interpretation of reg based on always-block style

data flow
Synthesis interpretation of reg based on always-block stylereg q;declaration@(posedge clk)?clocked or combinational?FLOPD flop with clock-enable@(*)?combinationalfull coverage?every branch assigns reg?WIREcombinational logicLATCHtransparent latch — buginitial?testbench-onlyno HWsimulation-only
Three questions decide the hardware. (1) Is the always sensitivity list on a clock edge? Yes → flop. (2) Is the always sensitivity list on @(*)? If so, does every branch assign the reg? Full coverage → wire. Incomplete → latch. (3) Is the reg only in an initial block? Then no hardware (testbench-only).

6.2 Visual B — blocking vs non-blocking timing

A side-by-side simulator-event view. Two always blocks process the same input; one uses blocking, the other non-blocking.

Blocking vs non-blocking assignment in clocked alwaysBLOCKINGx = a; y = x;After edgex = a, y = a (both = a)SynthesisONE flop (just to a)NON-BLOCKINGx <= a; y <= x;After edgex = a, y = old_x (shift!)SynthesisTWO flops (shift register)12
Two clocked always blocks, each with two assignments. The blocking version: x = a (immediate); y = x (uses new x = a). The non-blocking version: x <= a (deferred); y <= x (uses OLD x). After both clock edges, the blocking version has y = a (single stage); the non-blocking version has y = original_x → x = a (two-stage shift register).

6.3 Visual C — memory declaration

A reg [N-1:0] mem [0:M-1] declaration creates a 2-dimensional storage: M entries, N bits each. The synthesis tool maps this to either a register file (small M) or an inferred SRAM (large M, depending on the tool and library).

Memory declaration — array of regsreg [7:0] mem[0:255];256 × 8-bit storagemem[0]8-bit regmem[1]8-bit reg...mem[255]8-bit reg12
A memory declaration introduces a second dimension. reg [7:0] mem [0:255] is 256 entries × 8 bits — 2048 storage bits total. Synthesis typically maps this to a register file (256 flops × 8 bits = 2048 flops) for small sizes, or infers an SRAM macro for larger sizes. The two access dimensions: mem[addr] selects an entry; mem[addr][bit_idx] selects a bit within the entry.

7. The Memory (Array) Form

A reg with two bracket dimensions declares a memory — an array of regs. The syntax:

memory-syntax.v — memory declarations
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
// Standard form: reg [width-1:0] name [low:high];
reg [7:0]  mem      [0:255];      // 256 × 8-bit byte-addressable memory
reg [15:0] coeffs   [0:31];       // 32-entry × 16-bit coefficient table
reg [31:0] reg_file [0:31];       // 32 × 32-bit GPR-style register file

The first dimension (before the name) is the word width — the size of each entry. The second dimension (after the name) is the depth — the number of entries. The address selector is the second dimension's index; the value at that address is the first-dimension'd word.

7.1 Accessing memory

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mem[5]            // the 8-bit value at address 5
mem[5][3]         // bit 3 of the 8-bit value at address 5
mem[addr]         // the entry at address `addr` (dynamic indexing)
mem[addr][7:4]    // the upper 4 bits of the entry at addr

Slicing a memory entry is straightforward. Slicing across memory entries (e.g., mem[3:5] to get three entries) is not allowed — Verilog requires accessing one entry at a time.

7.2 Memory in a register file

A 32-entry × 32-bit register file written with reg:

rtl/regfile.v — 32-entry × 32-bit register file
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module regfile (
    input  wire        clk,
    input  wire        wr_en,
    input  wire [4:0]  wr_addr,
    input  wire [31:0] wr_data,
    input  wire [4:0]  rd_addr,
    output wire [31:0] rd_data
);
    reg [31:0] rf [0:31];            // 32 × 32-bit storage
 
    // Synchronous write
    always @(posedge clk) begin
        if (wr_en) rf[wr_addr] <= wr_data;
    end
 
    // Asynchronous read
    assign rd_data = rf[rd_addr];
endmodule

Synthesis output: 32 × 32-bit = 1024 flops + a 32-way 32-bit mux for the read port. For larger memories, the synthesis tool may infer an SRAM macro instead of discrete flops; the inference rules vary by tool and depend on the memory access pattern.

7.3 Memory has no reset

Memories are large; resetting every cell to a defined value would require sequential reset logic spanning many cycles. Verilog spec leaves memory at X on every bit at t = 0. In synthesisable RTL, the discipline:

  • For small memories (register files), explicit reset is often done — a generate loop in the reset path zeros every entry.
  • For large memories (SRAMs), no reset — the memory starts in an undefined state, and downstream logic assumes "the memory contains whatever was last written, and the design only reads addresses it has previously written."

The 5.2.4 sub-page covers arrays and memories in more depth; this page introduces the syntax and the typical reset discipline.

8. Canonical Patterns

Six patterns that account for 95% of all reg usage in production RTL.

8.1 The clocked enable flop

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)       count_q <= 8'h00;
    else if (load_en) count_q <= load_value;
    else if (incr_en) count_q <= count_q + 8'h01;
end

Pattern: asynchronous reset, two synchronous enables, default hold. The synthesis tool produces a D flop with a clock enable computed as load_en | incr_en, and the mux at the D input selects between load_value and count_q + 1.

8.2 The synchronous reset flop

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) begin
    if (sync_rst)   data_q <= 8'h00;
    else            data_q <= data_in;
end

Pattern: no asynchronous reset; reset is sampled on the clock edge like any other input. Some teams prefer this style for power-conscious designs (no asynchronous reset path; the reset net doesn't toggle every cycle). Synthesis produces a flop with the reset value muxed into the D input.

8.3 The combinational case-decoder

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(*) begin
    case (opcode)
        4'h0:    alu_op = 4'h0;     // NOP
        4'h1:    alu_op = 4'h1;     // ADD
        4'h2:    alu_op = 4'h2;     // SUB
        4'h3:    alu_op = 4'h3;     // AND
        default: alu_op = 4'hX;     // illegal — propagate X for debug
    endcase
end

Pattern: combinational decoder with a default branch. The default: alu_op = 4'hX; is a debug-only assignment — in simulation, propagating X makes illegal opcodes visible; in synthesis, the X is treated as "don't care" and the tool may optimise it to any value. Some teams replace the X with a defined "error" value (e.g., 4'hF) to keep gate-level simulation behaviour predictable.

8.4 The FSM state register

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) state_q <= IDLE;
    else        state_q <= state_next;
end
 
always @(*) begin
    state_next = state_q;                       // default: stay
    case (state_q)
        IDLE:    if (start) state_next = BUSY;
        BUSY:    if (done)  state_next = IDLE;
        default:            state_next = IDLE;
    endcase
end

Pattern: two-process FSM. The clocked always block stores state_q; the combinational always block computes state_next based on the current state_q and inputs. The default state_next = state_q; at the top covers any unhandled case (preventing latches).

8.5 The shift register

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) {q2, q1, q0} <= 3'b000;
    else        {q2, q1, q0} <= {q1, q0, d};
end

Pattern: 3-stage shift register. The concatenation on both sides of the <= makes the shift explicit. Each clock edge, every stage takes the value of the previous stage.

8.6 The combinational mux

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(*) begin
    case (sel)
        2'b00:   y = a;
        2'b01:   y = b;
        2'b10:   y = c;
        default: y = d;             // 2'b11 + any unexpected
    endcase
end

Pattern: combinational mux with default. The default covers every sel value not explicitly listed (including X and Z propagation). Synthesis produces a 4-input mux.

9. Simulation Behavior

The simulator schedules reg updates according to the IEEE 1364-2005 stratified scheduler:

  • Blocking assignments (=) execute in the Active region. The assignment happens immediately; subsequent statements in the same block see the new value.
  • Non-blocking assignments (<=) capture the RHS in the Active region and defer the actual write to the NBA region.
  • NBA region runs strictly after all Active events in the same time slot. All non-blocking assignments fire before the next time slot.
  • Continuous reads of reg (e.g., another always block sampling the reg) see the current value in whichever region they execute.

The practical consequence for a working engineer: in a clocked always block, every <= reads its RHS at the clock edge and the writes happen "at the same instant" in the NBA region. This is what makes the language model "every flop samples its D input, then every flop updates its Q output" correctly.

10. Waveform Analysis

A waveform comparing blocking vs non-blocking assignment in a clocked always — the canonical shift-register example.

Blocking vs non-blocking in a clocked always — shift register behaviour

12 cycles
Blocking vs non-blocking in a clocked always — shift register behaviourFirst edge: blk_x=blk_y=1 (both = a). nbl_x=1, nbl_y=X (old_x)First edge: blk_x=blk_…blk version: y always = x always = a (single stage)blk version: y always …nbl_y now = 1 (the previous nbl_x value, 1 cycle delayed)nbl_y now = 1 (the pre…Shift register visible: nbl_y trails nbl_x by 1 cycleShift register visible…clkablk_xXblk_yXnbl_xXnbl_yXXt0t1t2t3t4t5t6t7t8t9t10t11
Two clocked always blocks process the same input. The blocking version (blk_x, blk_y) collapses to a single point-to-point connection — both regs always equal a. The non-blocking version (nbl_x, nbl_y) forms a 2-stage shift register — nbl_y trails nbl_x by exactly one cycle. The keyword reg is the same in both; the only difference is = vs <=.

The waveform makes the cost of mixing the two operators visible: writing blk_x = a; blk_y = blk_x; in a clocked always produces single-stage point-to-point hardware, not a shift register. Engineers reaching for = in a clocked block to "fix" a timing issue often end up collapsing what they thought was a multi-stage pipeline into one stage.

11. Common Mistakes

Six pitfalls that catch beginners and intermediate engineers alike.

11.1 Using = in a clocked always

The §10 waveform pitfall. = in always @(posedge clk) is simulator-dependent — different simulators may produce different results because the order of always blocks is unspecified by the language. Always use <= for clocked assignment.

11.2 Using <= in a combinational always

The mirror image. <= defers the write to the NBA region, which means a combinational always block's outputs update one full time slot late. Downstream logic that reads the variable in the same time slot sees the old value, producing simulation timing issues that don't show up in gate-level simulation. Always use = for combinational assignment.

11.3 Incomplete coverage → latch

The "I'll add the else later" mistake. Every reg in always @(*) must be assigned in every branch. The default-at-top idiom (reg = default_val; if (...) reg = ...;) is the cleanest fix.

11.4 Mixing = and <= in the same block

Both operators in the same always block produces undefined behaviour across simulators. Pick one per block based on the block's purpose (clocked vs combinational) and stick with it.

11.5 Forgetting that memory has no reset

A reg [N-1:0] mem [0:M-1] declaration starts at X on every bit. Reading from a memory before writing it produces X-propagation. The discipline: small memories (register files) reset explicitly via a generate loop; large memories (SRAMs) rely on "only read addresses you've previously written."

11.6 Using initial values in synthesisable RTL

reg q = 1'b0; is legal but not synthesisable in most flows. The synthesis tool ignores the initial value, and the silicon's reset path determines the actual power-on state. Use the initial-value syntax only in testbench code; in synthesisable RTL, reset every reg through the asynchronous-reset or synchronous-reset path.

12. Industry Use Cases

Three patterns that account for the vast majority of reg usage:

12.1 The pipelined flop bank

Every pipelined RTL block (e.g., a 5-stage processor pipeline) declares each pipeline-stage's outputs as reg. Each stage's always @(posedge clk) writes the stage's outputs from the previous stage's outputs. The pattern is universal across every RTL design.

12.2 The register file

GPRs, configuration registers, status registers — every collection of named storage in a design is a reg [N-1:0] rf [0:M-1] memory. The 32 × 32-bit RISC-V GPR is the canonical example; the same pattern applies to AHB / APB peripheral registers, FSM-controlled state tables, and any "N storage cells indexed by an M-bit address" structure.

12.3 The FSM state register

Every finite-state machine has a state register: reg [W-1:0] state_q;. The clocked always updates state_q to state_next; the combinational always computes state_next from state_q and the inputs. The two-process FSM pattern is the standard idiom for synthesisable state machines.

13. Debugging Lab

Three reg debug post-mortems

14. Coding Guidelines

  1. Use reg for any signal assigned in always or initial. The keyword marks "procedurally assigned"; the always block decides the hardware.
  2. <= in clocked always, = in combinational always. Never mix in the same block.
  3. Reset every reg unconditionally in clocked always. Missing reset → X-propagation.
  4. Full coverage in combinational always. Every reg assigned in every branch. Default-at-top is the cleanest pattern.
  5. Use _q suffix for clocked outputs. count_q, state_q, data_q — the convention signals "this is a flop output" at a glance.
  6. Use _next or _d for combinational always outputs feeding flops. state_next, count_d — pairs with the _q suffix to make the flop/combinational split visible.
  7. Memory: reset small ones, design around large ones. Register files reset via generate loop; SRAMs rely on architectural "no read before write."
  8. No initial values in synthesisable RTL. reg q = 1'b0; is testbench-only. Synthesisable reset path determines the silicon initial state.

15. Interview Q&A

16. Exercises

Three exercises that turn the synthesis rule into reflex.

Exercise 1 — Identify the synthesis outcome

For each snippet, predict: flop, latch, combinational wire, or error.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// (a)
always @(posedge clk) q <= d;
 
// (b)
always @(*) q = d;
 
// (c)
always @(*) if (en) q = d;
 
// (d)
always @(*) begin q = 8'h00; if (en) q = d; end
 
// (e)
always @(posedge clk) q = d;       // = in clocked block
 
// (f)
assign q = d;                       // where q is declared as reg

Hints. Use the §6.1 decision tree. (a) clocked, <= → flop. (b) combinational with full coverage → wire. (c) combinational with incomplete coverage → latch. (d) default at top + override → wire. (e) = in clocked block → simulator-dependent (works but breaks shift-register semantics). (f) syntax error.

Exercise 2 — Convert latch to mux

The following module produces a latch warning. Rewrite it to produce a combinational mux:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module priority_select (
    input  wire        rd_en,
    input  wire        wr_en,
    input  wire [7:0]  data,
    output reg  [7:0]  q
);
    always @(*) begin
        if (rd_en)      q = data;
        else if (wr_en) q = ~data;
        // missing case → latch
    end
endmodule

Hints. Add a default value at the top of the block, or add an else branch. Both fixes produce a mux.

Exercise 3 — Implement a 32-entry register file

Write a Verilog module regfile32 with synchronous write and asynchronous read:

  • Inputs: wire clk, wire wr_en, wire [4:0] wr_addr, wire [31:0] wr_data, wire [4:0] rd_addr_a, wire [4:0] rd_addr_b.
  • Outputs: wire [31:0] rd_data_a, wire [31:0] rd_data_b.
  • Behaviour: write wr_data to rf[wr_addr] on posedge clk when wr_en. Read rf[rd_addr_a] and rf[rd_addr_b] combinationally.

Hints. Declare reg [31:0] rf [0:31]; (32 × 32-bit memory). Clocked always for the write. assign for the read.

17. Summary

reg is the procedural-assignment target in Verilog — the storage for variables written by always and initial blocks. The keyword marks "this signal is set by procedural code"; the always block decides what hardware the synthesis tool generates.

The four synthesis outcomes:

  • Flip-flop — clocked always (@(posedge clk)) with <= assignment.
  • Combinational wire — combinational always (@(*)) with = and full branch coverage.
  • Transparent latch — combinational always with incomplete branch coverage (usually a bug).
  • No hardwareinitial block only (testbench-only).

The blocking-vs-non-blocking rule:

  • Clocked always uses <= (non-blocking, NBA region, sample-then-update).
  • Combinational always uses = (blocking, Active region, immediate propagation).
  • Don't mix in the same block.

The declaration anatomy:

  • reg [N-1:0] name; — N-bit vector, unsigned, default X at t = 0.
  • reg signed [N-1:0] name; — N-bit signed.
  • reg [N-1:0] name [0:M-1]; — memory of M entries × N bits.

The day-to-day discipline:

  • Reset every reg in clocked always. Missing reset → X-propagation at gate level.
  • Cover every branch in combinational always. Default-at-top is the cleanest fix.
  • Use <= for clocked, = for combinational. One operator per block.
  • _q suffix for clocked outputs. Signals "this is a flop output" at a glance.
  • No initial values in synthesisable RTL. Reset path determines the silicon initial state.
  • Memory has no implicit reset. Small memories reset via generate loop; large memories rely on architectural "no read before write."

The sibling sub-pages cover the rest of the variable family: 5.2.2 integer (32-bit signed loop variables), 5.2.3 real (testbench-only floating-point), and 5.2.4 Scalar vs Vector vs Arrays (the dimensions every variable can take). Chapter 6 picks up with constant variables (parameter / localparam), and Chapter 7 covers compiler directives.