Verilog · Chapter 5.2 · Data & Variables
Register Data Types in Verilog
The first half of Chapter 5 covered the net side of Verilog, the language's model for physical interconnect. This section covers the other half, variables. A variable holds a value the procedural code assigns to it, and the simulator stores it across time and across clock edges. Verilog gives variables their own keywords such as reg, integer, and real, their own assignment statements inside always and initial blocks, and their own type system. The most confused name in the language sits at the top of this section, because reg does not mean register. A reg is a procedural-assignment target, and whether it becomes a flip-flop, a latch, combinational logic, or no hardware depends entirely on how the code writes to it. This page is the section overview and the synthesis rule every RTL engineer carries by reflex.
Foundation20 min readVerilogregintegerrealVariablesProcedural
Chapter 5 · Section 5.2 · Data & Variables
1. The Engineering Problem
A junior engineer writes their first piece of synthesisable Verilog. The intent is a synchronous up-counter:
module counter (
input wire clk,
input wire rst_n,
output reg [7:0] count_q
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) count_q <= 8'h00;
else count_q <= count_q + 8'h01;
end
endmoduleA second engineer writes what they think is the same counter but uses reg differently:
module counter_broken (
input wire clk,
input wire rst_n,
input wire [7:0] count_in,
output reg [7:0] count_q
);
always @(*) begin
count_q = count_in + 8'h01;
end
endmoduleBoth modules declare count_q as reg [7:0]. The first synthesises to an 8-bit flip-flop with an asynchronous reset. The second synthesises to an 8-bit combinational adder — no flip-flop, no storage, no clock. The keyword reg is identical; the resulting hardware is different. The synthesis tool decided based on the always block style, not the keyword.
This is the single most-confused part of Verilog. Every textbook says "reg is for registers" and every working RTL engineer has to unlearn that and replace it with the real rule: reg is a procedural-assignment target. What it becomes after synthesis depends on the procedural code. The same keyword can produce a flop (clocked always), a latch (combinational always with incomplete assignments), a wire (combinational always with complete assignments), or no hardware at all (testbench-only reg driving stimulus).
The variable side of Verilog — reg, integer, real — is the second half of Chapter 5. This page is the section overview: what variables are, how they differ from nets, the four data types the section covers, and the synthesis-interpretation rule that decides what hardware each one becomes.
2. Why Verilog Has Two Type Families
The net side of Chapter 5 (5.1.x) covered constructs whose value is determined by continuous resolution over multiple drivers — every cycle, the simulator runs a resolution function over the active drivers and computes the net value. Nets model physical interconnect; their drivers are continuous-assignment statements (assign) or module-instance outputs.
The variable side of Chapter 5 (5.2.x) covers constructs whose value is determined by procedural assignment — explicit statements inside always or initial blocks that set the variable's value as the procedural code executes. Variables model the simulator's algorithmic state. Their assignments are = (blocking) or <= (non-blocking) statements inside procedural blocks.
The two halves of the language exist because hardware has two distinct kinds of signals:
- Wires carry values produced by combinational logic — the value at any instant is determined by the current inputs, with no memory. A
wirematches this electrical model directly. - State carries values produced by sequential logic — the value at any instant depends on the previous value and the conditions under which it was written. A flop or latch matches this model directly, and Verilog's
regis the language's procedural-assignment target that the synthesis tool maps to flops, latches, or combinational logic depending on how the code writes to it.
Confusing the two is the bedrock mistake of beginner Verilog. You cannot drive a wire from inside an always block (the compiler errors out). You cannot drive a reg from a continuous assign (compiler error again). The language enforces the type-family split syntactically; the synthesis tool then interprets the procedural code to decide what hardware to generate.
3. Mental Model
The mental-model has four practical consequences:
regis a syntactic category, not a hardware category. The synthesis tool reads the always block to decide what hardware to generate; the keyword's role is to mark "this signal is procedurally assigned" (vswire, which marks "this signal is continuously assigned").- You can declare a
regand never have it synthesise to hardware. Testbench-onlyregs (stimulus generators, scoreboards) live entirely in the simulator's memory and don't appear in the netlist. - You can declare a
regthat synthesises to a wire. Aregassigned by a combinationalalways @(*)with full coverage of all input combinations becomes a piece of combinational logic — no flop, no latch, no storage in the gates. - The reverse is also true: you can have storage without a
reg. Some constructs (thetaskandfunctionargument bindings;automaticvariables in tasks) introduce variables without explicitregdeclarations.
4. The Three Variable Types
Verilog defines three variable data types. Each has a distinct purpose and a distinct synthesis story.
| Type | Purpose | Width | 4-state / 2-state | Synthesis |
|---|---|---|---|---|
reg | General-purpose procedural-assignment target | Configurable ([N-1:0]) | 4-state (0, 1, Z, X) | Maps to flop / latch / wire based on always block |
integer | Signed arithmetic; loop variables; counters in testbenches | Fixed 32 bits, signed | 4-state | Synthesisable but unusual — typically used in testbenches and generate loops |
real | Floating-point values; analog modelling; testbench stimulus | 64-bit IEEE 754 (host platform) | 2-state (no X / Z) | Not synthesisable — simulation-only |
The next four sub-topics (5.2.1–5.2.4) drill into the three types and into the dimensions (scalar / vector / array) that shape every one of them:
- 5.2.1
reg— the workhorse. Procedural-assignment target with explicit width. Becomes flop / latch / wire depending on the always block. - 5.2.2
integer— 32-bit signed. The "I just need a counter" variable; primarily testbench /for-loop usage; synthesisable with caveats. - 5.2.3
real— floating-point. Simulation-only. Used for analog modelling, timing-extraction post-processing, and testbench analytics. - 5.2.4 Scalar vs Vector vs Arrays — the three dimensions a variable can take. Scalar (1 bit), vector (multi-bit packed
[N-1:0]), array (multiple instances[M-1:0]after the name).
The section's structure mirrors how engineers reach for the types in real work: reg for almost everything, integer for testbench iteration and counter variables, real for analytics, and the dimensions topic for the array vs vector decision that turns a one-bit register into an 8-bit bus or a 1024×8 memory.
5. The Net-vs-Variable Rules
The language enforces strict rules about where each type family can be used. Violating a rule produces a compile error, not a runtime surprise — which is fortunate, because the synthesis interpretation downstream would otherwise be impossible.
| Context | Allowed type | Reason |
|---|---|---|
Left side of assign | net (wire, tri, tri0, etc.) | Continuous assignment produces a continuously-resolved value |
Left side of = / <= inside always / initial | variable (reg, integer, real) | Procedural assignment requires storage |
| Right side of any assignment | either | Reading a value imposes no constraint |
| Module input port | net (wire by default) | Driven by the parent module's continuous expression |
| Module output port | either (wire or reg) | A reg output port is set by an internal always; a wire output port is set by a continuous assignment |
| Module inout port | net only | Bidirectional ports must support contention-aware resolution |
Sensitivity list of always @(...) | either | Read-only context |
Initial value of a declaration (reg [7:0] x = 8'h00;) | variable only | The initial value is a procedural assignment at t = 0 |
for loop iteration variable | variable (integer or reg) | Loop variables are procedurally assigned each iteration |
Three rules a working engineer carries by reflex:
wirefor combinational interconnect. Anywhere you'd writeassign, the target is awire. Never areg.regfor procedural storage. Anywhere you'd write inside analwaysorinitialblock, the target is areg(orinteger/real). Never awire.output regfor module outputs set by an internalalways. The most common port-list pattern in RTL — drive the output from a clocked always block.
The fourth rule is the synthesis rule that converts the procedural code to hardware:
- A
regassigned insidealways @(posedge clk)becomes a flip-flop. - A
regassigned insidealways @(*)with all branches assigning all output variables becomes combinational logic (no storage). - A
regassigned insidealways @(*)with missing assignments in some branches becomes a latch. (Almost always a bug — see §10.)
The first sub-topic (5.2.1) drills into these synthesis rules for reg specifically; this page introduces them at the framing level.
6. Visual Explanation
Three figures cover the structural picture.
6.1 Visual A — the type-family tree
The two halves of Chapter 5 side by side. Section 5.1 covered the left half (nets); section 5.2 covers the right half (variables).
Verilog data type families — nets (5.1) and variables (5.2)
data flow6.2 Visual B — net vs variable assignment
Side-by-side: the same logical signal, modelled as a wire (continuous assignment) and as a reg (procedural assignment). The two produce the same hardware behaviour in this simple case, but the language constraints differ.
6.3 Visual C — what reg becomes after synthesis
The same reg declaration, in three different always-block contexts, produces three different pieces of hardware. The keyword does not decide; the always block does.
7. The Canonical reg Patterns
Three short examples cover the three synthesis outcomes. Every working engineer recognises these patterns by reflex.
7.1 reg → flip-flop (clocked always)
`default_nettype none
module up_counter (
input wire clk,
input wire rst_n,
input wire en,
output reg [7:0] count_q // reg in the port list, driven by clocked always
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) count_q <= 8'h00; // asynchronous reset
else if (en) count_q <= count_q + 8'h01;
end
endmoduleThe always @(posedge clk or negedge rst_n) sensitivity list signals "clocked process" to the synthesis tool. The <= non-blocking assignment inside the block is the canonical flop-write idiom. The synthesis tool produces an 8-bit flip-flop bank with asynchronous-reset capability — one flop per bit, 8 D-FFs in the netlist.
Three discipline rules in this pattern:
- Use
<=for clocked assignment. The non-blocking assignment makes the synthesis tool's job unambiguous (flop input, sample on posedge clk) and produces deterministic simulation across simulators. - Reset every reg unconditionally. Every flop in the design should reset to a defined value during
!rst_n; missing reset paths produce X-propagation issues at gate-level simulation. - Either reset or hold the previous value. The
else if (en)pattern is correct: whenen=0, the flop simply holds its previous value because there's no assignment for that case. The synthesis tool infers a clock-enable flop, which is the right hardware.
7.2 reg → combinational (always @(*) with full coverage)
`default_nettype none
module adder_decoder (
input wire [3:0] a,
input wire [3:0] b,
input wire sub_n_add,
output reg [4:0] result // reg in port list, driven by always @(*)
);
always @(*) begin
if (sub_n_add) result = {1'b0, a} - {1'b0, b}; // = sign-extend, subtract
else result = {1'b0, a} + {1'b0, b}; // = sign-extend, add
end
endmoduleThe always @(*) sensitivity list signals "combinational process." The = blocking assignment is the canonical combinational idiom. Both branches assign result — full coverage of every input combination. The synthesis tool produces a 5-bit adder / subtractor mux — no flop, no latch, no storage. Same hardware as assign result = sub_n_add ? ... : ...; would produce on a wire.
The pattern is useful when the combinational expression is too complex for a single assign (e.g., a case statement, a multi-branch decoder, a state-encoded mux). The choice between assign on a wire and always @(*) on a reg is mostly stylistic for this case — same gates either way.
7.3 reg → latch (always @(*) with incomplete coverage)
`default_nettype none
module unintended_latch (
input wire en,
input wire [3:0] data_in,
output reg [3:0] data_out // INTENDED combinational; ACTUAL latch
);
always @(*) begin
if (en) data_out = data_in;
// BUG: no else branch → data_out is not assigned when en=0
// → synthesis infers a latch to "remember" the value
end
endmoduleThe always @(*) sensitivity list signals combinational intent. But the procedural code only assigns data_out when en=1; when en=0, the code path doesn't touch data_out, so the simulator's value of data_out is whatever it was on the previous activation of the block. The synthesis tool interprets this as "storage required" and produces a transparent latch.
A latch is almost always a bug. Latches:
- Are transparent (pass-through) during the active level of the enable, then hold during the inactive level — they make timing analysis much harder than a flop.
- Are forbidden by most ASIC sign-off rules — the synthesis tool will emit a lint warning ("LATCH inferred at signal
data_out") that should be treated as an error. - Are easy to write accidentally — any incomplete-coverage
iforcasein a combinationalalwaysproduces one.
The fix is to assign every output in every branch:
always @(*) begin
if (en) data_out = data_in;
else data_out = 4'b0000; // explicit default value
endOr, equivalently, set the default at the top of the block:
always @(*) begin
data_out = 4'b0000; // default: zero
if (en) data_out = data_in; // override when en=1
endEither form gives the synthesis tool full coverage and produces combinational logic (a 4-input mux), not a latch. The 5.2.1 reg sub-page covers this rule in detail with worked examples and lint outputs.
8. Simulation Behavior
Variables in Verilog have three behaviours that distinguish them from nets:
- Procedural assignment, not resolution. A variable's value changes only when a procedural statement (
=or<=) assigns to it. There is no resolution function — the most-recent assignment wins. - Default value is
X. Att = 0, everyregandintegerstarts atX(unknown) on every bit.realstarts at0.0(noXrepresentation for floats). The right RTL discipline: reset everyregin synthesisable code; in testbenches, initialise everyregin theinitialblock before sampling. - Held across clock edges and time slots. A variable's value persists from the time it's last assigned until the next assignment. This is what makes it useful for modelling state — and what makes "I forgot to reset this
reg" produce X-propagation issues.
The 5.2.1 sub-page covers the blocking-vs-non-blocking distinction in depth; this page introduces it at the framing level.
9. Waveform Analysis
A short waveform showing the same logical behaviour modelled three ways — as a wire, as a clocked reg, and as a combinational reg. The wire and combinational reg track their inputs continuously; the clocked reg samples its input on the clock edge.
wire / clocked reg / comb reg — three modelling styles, two distinct behaviours
12 cyclesThe waveform captures the synthesis-interpretation rule visually: the keyword does not matter for the wire-vs-comb-reg comparison (both produce the same output every cycle); the always-block style matters for the wire-vs-clocked-reg comparison (the clocked reg has memory; the wire doesn't).
10. Common Mistakes
Five pitfalls that catch every Verilog beginner.
10.1 "reg means register"
The textbook framing every beginner reads, and the wrong one. reg is a procedural-assignment target. The synthesis tool decides whether it becomes a register, a latch, a wire, or no hardware at all — based on the always block, not the keyword. Replace "reg means register" with "reg means procedurally assigned" in your mental model.
10.2 Mixing = and <= in the same always block
The blocking-vs-non-blocking distinction matters. Inside a clocked always, every assignment should be <= (deferred write, samples-then-updates semantic). Inside a combinational always, every assignment should be = (immediate write, propagates within the block). Mixing produces unpredictable behaviour across simulators and confuses every reader.
10.3 Incomplete combinational coverage → latch
The "I'll add the else branch later" mistake. Every reg assigned in a combinational always @(*) must be assigned in every branch of every if / case statement. Missing any branch produces a latch — usually unintended. The two fixes (explicit else, or default-at-top-of-block) cover every case. The 5.2.1 sub-page treats this as a first-class topic.
10.4 Using = for clocked storage
The = blocking assignment in a clocked block produces simulation that depends on the order of always blocks in the file — unspecified by the language. Different simulators may produce different results. Always use <= for clocked assignment; the deferred-write semantic guarantees correct sample-and-update behaviour across every simulator.
10.5 Forgetting that integer is signed and 32-bit
Verilog's integer is always 32-bit signed — not the platform's int, not a configurable width. Using integer for a value that should be unsigned (e.g., a memory address) produces unexpected sign-extension behaviour when the value reaches 2³¹. Use reg [31:0] (or whatever width is correct) for unsigned values; reserve integer for loop variables and small testbench counters.
11. Industry Use Cases
Three patterns that account for 95% of all variable usage in working RTL.
11.1 reg for every flop in the design
Every RTL block has flops. Every flop is declared reg in the language's eyes. The pattern is universal — Synopsys IP, Apple Verilog, NVIDIA RTL, every ARM core ever shipped. The reg keyword on a clocked always's target is the most common single line of Verilog in any production codebase.
11.2 reg for combinational always blocks
When a combinational expression is too complex for an assign — e.g., a case-statement decoder, a multi-stage mux, a finite-state machine's next-state logic — the working pattern is a combinational always @(*) with reg outputs. Pure decoders, ALU output selection, and FSM next-state logic almost always live in always @(*) blocks. The synthesis tool produces the same gates as the equivalent assign form, but the readability is better for multi-line logic.
11.3 integer for testbench iteration
Testbench initial blocks frequently iterate over a range of values — applying stimulus, sweeping through configuration parameters, walking through a memory. integer i; followed by a for (i = 0; i < N; i = i + 1) is the canonical loop construct. Synthesis-side use of integer in generate blocks for iteration is also common, but genvar (Chapter 14) is preferred for generate because it's purely a compile-time iterator.
12. Debugging Lab
Three register-data-type debug post-mortems
13. Coding Guidelines
- Use
regfor any signal assigned insidealwaysorinitial. The keyword marks "procedurally assigned"; the synthesis tool decides what hardware to build. - Use
wirefor any signal driven byassignor a module output port. The keyword marks "continuously resolved"; the synthesis tool produces interconnect. - Clocked always blocks use
<=. Combinational always blocks use=. Mixing inside the same block produces unpredictable behaviour across simulators. - Every
regin synthesisable RTL is either reset unconditionally or assigned in every branch. Missing reset produces X-propagation; missing branch coverage produces latches. - Use
integeronly for loop variables and small testbench counters. For unsigned values or widths other than 32, usereg [N-1:0]. realis testbench-only. Synthesis tools rejectrealdeclarations; reserve it for analytics, BFM models, and stimulus generation.output regfor ports driven by an internal clocked always.output wirefor ports driven by an internalassign. The port-list discipline tells the next engineer how the signal is driven.- Name
regoutputs of clocked alwayses with_qsuffix. The conventioncount_q,state_q,data_qsignals "this is a flop output" at a glance — separates the storage from the combinational logic that drives it.
14. Interview Q&A
15. Exercises
Three exercises that turn the variable model into reflex.
Exercise 1 — Identify the synthesis outcome
For each of the following code snippets, predict what the synthesis tool produces: flop, latch, combinational wire, or error.
// (a)
always @(posedge clk) q <= d;
// (b)
always @(*) y = a & b;
// (c)
always @(*) if (en) y = data;
// (d)
always @(*) begin
y = 1'b0;
if (en) y = data;
end
// (e)
assign q = d; // where q is declared as regHints. Use the §7 rules. (a) is a clocked block. (b) is a combinational always with full coverage. (c) is combinational with incomplete coverage. (d) has a default at the top. (e) is a syntax mismatch.
Exercise 2 — Convert latch to mux
The following module produces a latch warning during synthesis. Rewrite it so the synthesis tool produces a combinational mux instead. Keep the logical behaviour the same.
module decoder_with_latch (
input wire [1:0] sel,
input wire [7:0] data_a,
input wire [7:0] data_b,
input wire [7:0] data_c,
output reg [7:0] data_out
);
always @(*) begin
case (sel)
2'b00: data_out = data_a;
2'b01: data_out = data_b;
2'b10: data_out = data_c;
// 2'b11: missing case — latch inferred for data_out
endcase
end
endmoduleHints. Either add a default branch to the case, or set data_out at the top of the block before the case. Both fixes produce a mux instead of a latch.
Exercise 3 — Choose the right type
For each of the following signals, recommend whether to declare it as reg [N-1:0], integer, or wire [N-1:0]. Justify each choice.
| # | Signal description |
|---|---|
| 1 | An 8-bit counter that increments every clock cycle |
| 2 | The output of an assign statement implementing a & b |
| 3 | A loop iteration variable in a testbench for loop |
| 4 | A 16-bit address generated by a state machine |
| 5 | A floating-point delay value used in the testbench |
| 6 | The output of a combinational decoder in an always @(*) block |
Hints. Loop variables and small testbench counters → integer. Floating-point → real. Signals driven by procedural code → reg [N-1:0]. Signals driven by assign → wire [N-1:0].
16. Summary
Section 5.2 covers Verilog's variable types — the procedural-assignment targets that complement the net family from Section 5.1. The three variable types:
reg— general-purpose, configurable-width, 4-state. The workhorse. Synthesises to flop / latch / wire depending on the always block.integer— 32-bit signed, 4-state. Loop variables and small testbench counters.real— 64-bit IEEE 754 floating-point. Simulation-only.
The defining rule of the section — reg is not a register. The synthesis tool decides the hardware based on the always-block structure:
- Clocked always (
@(posedge clk),<=) → flip-flop. - Combinational always with full coverage (
@(*),=) → wire (no storage). - Combinational always with missing coverage (
@(*), incompleteif/case) → latch (usually a bug).
The net-vs-variable rules:
wireis set byassign; lives in combinational interconnect.regis set by=/<=insidealways/initial; lives in procedural code.- The two type families are syntactically distinct — mixing produces compile errors.
The day-to-day discipline:
- Use
regfor any signal assigned insidealwaysorinitial. The keyword marks "procedurally assigned." - Use
<=for clocked assignment,=for combinational. Don't mix in the same block. - Every
regis either reset or assigned in every branch. Missing reset → X-propagation. Missing branch coverage → latch. - Use
integeronly for loop variables and small testbench counters. For real RTL,reg [N-1:0]gives explicit control over width. realis testbench-only. Synthesis tools reject it.
The next four sub-pages drill into each type:
- 5.2.1
reg— the workhorse type, flop / latch / wire synthesis interpretation in depth. - 5.2.2
integer— 32-bit signed, testbench / loop usage, synthesis caveats. - 5.2.3
real— floating-point modelling for testbench analytics. - 5.2.4 Scalar vs Vector vs Arrays — the dimensions every variable can take.
After Section 5.2, Chapter 6 picks up with Constant Variables — the parameter and localparam keywords that anchor every parametrisable RTL block.
Related Tutorials
- Variables & Data Types — Chapter 5; the parent overview that motivates this section.
- Physical Data Types — Chapter 5.1; the sibling section on the net side.
- wire and tri Nets — Chapter 5.1.1; the canonical net type that contrasts with
reg. - RTL Designing — Chapter 3; the broader RTL design framing that explains the flop / combinational distinction.
- Lexical Conventions — Chapter 4; the syntax rules that every variable declaration follows.