Verilog · Chapter 5.2.2 · Data & Variables
integer in Verilog — 32-bit Signed Loop Variable
The integer type is Verilog's convenience variable for procedural code that doesn't need a specific width. It declares a 32-bit signed, four-state variable with fixed width and signedness, and it is used overwhelmingly for one purpose: loop iteration. Almost every for-loop counter in a testbench initial block, every generate-loop iterator, and every scratch counter that just needs to count reaches for integer. It does have a synthesizable form that maps to a 32-bit register, but using it for real RTL is almost always a mistake, because sign-extension surprises and unintended 32-bit width catch engineers who pick integer instead of a sized reg. This lesson explains why integer exists, how it differs from a plain 32-bit reg, and the narrow set of cases where it is the right tool.
Foundation18 min readVerilogintegerLoopSignedTestbench
Chapter 5 · Section 5.2.2 · Data & Variables
1. The Engineering Problem
Two engineers each write a 16-cycle delay counter that asserts done when the count expires:
// Engineer A — uses integer
module delay_counter_a (
input wire clk, rst_n, start,
output reg done
);
integer cnt; // 32-bit signed
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin cnt <= 0; done <= 1'b0; end
else if (start) begin cnt <= 16; done <= 1'b0; end
else if (cnt > 0) begin cnt <= cnt - 1; done <= (cnt == 1); end
end
endmodule
// Engineer B — uses reg [4:0]
module delay_counter_b (
input wire clk, rst_n, start,
output reg done
);
reg [4:0] cnt; // 5-bit unsigned
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin cnt <= 5'h00; done <= 1'b0; end
else if (start) begin cnt <= 5'd16; done <= 1'b0; end
else if (cnt > 0) begin cnt <= cnt - 5'd1; done <= (cnt == 5'd1); end
end
endmoduleBoth modules behave correctly in simulation. Both synthesise. The difference is in the generated netlist:
- Engineer A's
cntis a 32-bit signed register. Synthesis produces 32 flip-flops, a 32-bit subtractor forcnt - 1, and 32-bit comparators forcnt > 0andcnt == 1. The upper 27 bits are always0— wasted area. - Engineer B's
cntis a 5-bit unsigned register. Synthesis produces 5 flip-flops, a 5-bit subtractor, and 5-bit comparators. No wasted bits.
For a single counter, the difference is small (a few dozen flops). For a design with hundreds of counters, the cumulative cost is significant — area, power, and timing. The integer keyword's convenience (no width to think about) comes at the cost of always-32-bit hardware that's almost always wider than the actual range needs.
This is the central message of the integer type: the convenience is real, but the cost is real too. Use integer where the convenience pays off — testbench loops, generate-loop iterators, configuration scratch variables — and use reg [N-1:0] everywhere the actual width matters.
2. Anatomy of an integer
The full integer declaration syntax:
`default_nettype none
// integer name [= initial_value] [, name2, ...];
integer i; // scalar integer
integer cnt = 0; // with initial value (testbench-only)
integer x, y, z; // multiple in one statement
integer arr [0:7]; // array of 8 integers (rare)Four properties baked into the keyword:
- Width is fixed at 32 bits. No
[range]modifier; the width cannot be changed. - Signedness is fixed at signed. Two's complement, range
-2,147,483,648to+2,147,483,647. Nounsignedmodifier exists. - Value system is 4-state. Supports
0,1,Z,Xper bit — same asreg. The initial value att = 0isXon every bit (unless an explicit initial value is provided). - Procedural-assignment target. Like
reg, anintegeris assigned by=or<=insidealwaysorinitialblocks. Continuousassignis not legal.
The first three properties are what distinguish integer from reg [31:0] syntactically:
| Property | integer | reg [31:0] | reg signed [31:0] |
|---|---|---|---|
| Width | 32 bits (fixed) | 32 bits | 32 bits |
| Signedness | signed | unsigned | signed |
| Default initial value | X | X | X |
| 4-state | yes | yes | yes |
| Procedural target | yes | yes | yes |
| Synthesisable | yes (with caveats) | yes | yes |
The only functional distinction between integer and reg signed [31:0] is one is shorter to type. The synthesis tool treats them identically — both produce 32 flip-flops with signed arithmetic semantics. The choice between the two is stylistic for the synthesis case; for testbench use, integer is more conventional.
3. Mental Model
The mental-model has three consequences:
integerandreg signed [31:0]are interchangeable. Picking between them is a style choice for synthesisable code; for loops and scratch variables,integeris more idiomatic.integeris signed. Mixingintegerwith unsignedregin arithmetic produces sign-extension behaviour that catches every beginner. Use$unsigned()to mask the sign extension when needed (see §6).integeris always 32 bits. A counter that needs to count to 256 still consumes 32 flop bits if declared asinteger. The synthesis tool may optimise away the unused upper bits if it can prove they're never used (constant-propagation), but that optimisation is not guaranteed.
4. The Two Use Cases
integer shows up in two contexts in production Verilog. Outside of these, prefer reg [N-1:0].
4.1 Loop iteration in testbenches
The single most common use of integer. A testbench initial block iterates over a range of values to apply stimulus, check expected outputs, or sweep a configuration parameter. The loop variable is integer:
`timescale 1ns/1ps
`default_nettype none
module tb_alu;
reg [7:0] a, b;
reg valid;
wire [7:0] sum;
wire overflow;
alu dut (.a(a), .b(b), .valid(valid), .sum(sum), .overflow(overflow));
integer i, j; // loop iterators — testbench-only
initial begin
valid = 1'b0;
a = 8'h00;
b = 8'h00;
// Sweep every combination of a and b for the first 16 values
for (i = 0; i < 16; i = i + 1) begin
for (j = 0; j < 16; j = j + 1) begin
a = i[7:0];
b = j[7:0];
valid = 1'b1;
#10;
if (sum !== (a + b) || overflow !== ((a + b) > 8'hFF))
$display("MISMATCH at a=%h, b=%h: got sum=%h, exp=%h",
a, b, sum, a + b);
end
end
$finish;
end
endmoduleThe pattern is universal. Every testbench has at least one integer loop counter; many have several. The convenience of "no width to declare" matches the throwaway nature of loop variables.
4.2 Generate-loop iteration
The synthesisable counterpart. A generate block uses an iteration variable to replicate hardware structure — e.g., generating 32 identical instances of a sub-module:
`default_nettype none
module wide_adder #(parameter W = 32) (
input wire [W-1:0] a,
input wire [W-1:0] b,
output wire [W-1:0] sum,
output wire cout
);
wire [W:0] carry;
assign carry[0] = 1'b0;
genvar i; // genvar — strictly compile-time
generate
for (i = 0; i < W; i = i + 1) begin : g_fa
full_adder u_fa (
.a (a[i]),
.b (b[i]),
.cin (carry[i]),
.sum (sum[i]),
.cout (carry[i+1])
);
end
endgenerate
assign cout = carry[W];
endmoduleThe generate-loop iterator is conventionally genvar i; — NOT integer i;. The genvar keyword (covered in Chapter 14.7) is a strictly compile-time iteration variable; it cannot be used at simulation time, only by the elaborator to expand the generate loop into N copies of the instantiated block. Using integer for a generate loop is legal in some flows but flagged by lint as "use genvar for generate iteration"; the strict-discipline answer is genvar.
This page lists generate iteration as a use case for iteration variables in general — but for synthesisable RTL, the variable is genvar, not integer. The two share the convenience of "no width to declare" and serve similar roles.
4.3 What integer is NOT for
Three things integer is not the right type for in synthesisable RTL:
- Counters and addresses. Use
reg [N-1:0]with the exact width.reg [4:0] cnt;for a 0-to-31 counter;reg [11:0] addr;for a 4 KB address; etc. - Datapath registers. Use
reg [N-1:0]matching the actual data width.reg [31:0] data_q;for a 32-bit data path is fine; usinginteger data_q;is fine too, but the signed semantics will catch you out the first time you compare against an unsigned value. - FSM state registers. Use
reg [STATE_W-1:0]with the width that matches the encoded state. One-hot states might be 8-bit; binary-encoded states 3-bit. Usingintegerwastes flops and risks signed-comparison bugs.
5. The Sign-Extension Trap
The most common integer bug. When an integer (signed 32-bit) is assigned to a narrower reg (unsigned, smaller width), the sign extension produces unexpected results.
`default_nettype none
module sign_extension_bug (
input wire clk,
input wire rst_n,
output reg [7:0] data_q
);
integer cnt; // 32-bit signed
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= -1; // signed -1 = 32'hFFFF_FFFF in two's complement
data_q <= 8'h00;
end
else begin
cnt <= cnt + 1; // increment
// BUG: data_q = cnt produces silent truncation, but cnt's sign bit
// makes the truncation different from what the author expects.
// cnt = -1 → 32'hFFFF_FFFF → data_q = 8'hFF (correct)
// cnt = 0 → 32'h0000_0000 → data_q = 8'h00 (correct)
// cnt = 1 → 32'h0000_0001 → data_q = 8'h01 (correct so far)
// cnt = 256 → 32'h0000_0100 → data_q = 8'h00 (truncation — bit 8 lost)
data_q <= cnt[7:0];
end
end
endmoduleThis isn't really a sign-extension bug — it's a width-truncation bug — but the integer declaration makes the truncation surprising because most engineers carry "the variable's width" as a mental model, and integer invisibly forces that width to 32.
The fix in this case is to use reg [7:0] cnt; — explicit 8-bit width matches the use case, and the truncation problem disappears because the variable was 8-bit all along.
The real sign-extension trap appears when integer values are compared:
integer i;
reg [3:0] addr;
// ...
if (i == addr) ... // BUG: i is signed 32-bit, addr is unsigned 4-bit
// Verilog promotes addr to 32-bit unsigned for the comparison,
// then re-interprets the comparison as signed (because i is signed).
// For i = -1 (32'hFFFF_FFFF) vs addr = 4'hF (32'h0000_000F after promotion):
// signed comparison says -1 != 15 — FALSE.
// But the engineer probably wanted "is the 4-bit value equal to -1?"
// which would have been TRUE if both were unsigned.The IEEE 1364-2005 §4.5 width-extension and signed-vs-unsigned rules are precise but unintuitive. The working-engineer rule: never mix integer with unsigned reg in arithmetic or comparison. If you need a signed counter, use reg signed [N-1:0]. If you need an unsigned counter, use reg [N-1:0]. Picking integer because it's "convenient" is the path that produces the sign-extension bug six months later.
6. Forcing Unsigned Comparison with $unsigned()
When you genuinely need to compare a signed integer with an unsigned value, the IEEE 1364-2005 spec provides $unsigned() and $signed() system functions to convert between the two interpretations:
`default_nettype none
integer i;
reg [3:0] addr;
// ...
// Force unsigned comparison: $unsigned(i) treats i's bits as unsigned 32-bit.
// i = -1 (32'hFFFF_FFFF) → $unsigned(i) = 4,294,967,295
// addr = 4'hF, promoted to 32-bit = 15.
// Comparison: 4,294,967,295 != 15 → FALSE (still doesn't match).
// To compare the low 4 bits only:
if (i[3:0] == addr) ... // bit slice — explicit, no sign-extension issueThe $unsigned() cast is rarely the right answer. More often, the right answer is a bit slice — i[3:0] selects the low 4 bits of i regardless of its signedness, and the comparison happens on the 4-bit slice vs the 4-bit addr. Bit slicing is explicit and produces no surprises.
7. Visual Explanation
Three figures illustrate the integer vs reg [N-1:0] decision.
7.1 Visual A — width comparison
Same logical counter, two declarations. integer consumes 32 flops; reg [4:0] consumes 5 flops.
integer vs reg [4:0] — area comparison for a 0–16 counter
data flow7.2 Visual B — sign-extension trap
A signed integer (-1) assigned to an unsigned reg [7:0] produces 8'hFF because of two's complement representation. Same integer value (-1) compared against an unsigned reg [3:0] value of 4'hF does not match — Verilog promotes the 4-bit value to 32-bit unsigned (32'h0000_000F), then the signed-vs-unsigned comparison rules produce FALSE.
7.3 Visual C — the use-case decision tree
The decision flow for picking the variable type.
8. The Loop Iteration Pattern
The canonical integer use. A testbench initial block iterates over a range of values. The for loop syntax follows the C convention.
module tb_register_sweep;
reg [7:0] cfg_reg [0:15]; // 16-entry × 8-bit configuration table
integer i; // loop iterator
initial begin
// Initialize every entry to zero
for (i = 0; i < 16; i = i + 1) begin
cfg_reg[i] = 8'h00;
end
// Write a known pattern to each entry
for (i = 0; i < 16; i = i + 1) begin
cfg_reg[i] = i[7:0]; // bit slice prevents sign-extension warning
end
// Verify the pattern
for (i = 0; i < 16; i = i + 1) begin
if (cfg_reg[i] !== i[7:0])
$display("MISMATCH at i=%0d: cfg_reg=%h, expected=%h",
i, cfg_reg[i], i[7:0]);
end
$display("Test complete.");
$finish;
end
endmoduleThree patterns visible:
for (i = 0; i < N; i = i + 1)— the C-style for loop. Verilog requiresi = i + 1(no++operator); SystemVerilog addsi++.i[7:0]— bit-slice the loop iterator to match the target width. Without the slice,cfg_reg[i] = i;would assign a 32-bit value to an 8-bit reg and produce a width-mismatch lint warning.%0dformat — printias a decimal integer with no leading zeros. The%dformat would right-align to the integer's natural width (10 digits for 32-bit), making the output noisy.
The pattern scales — testbenches commonly have several integers, each used for one loop dimension. Nested loops (e.g., for stimulus matrices) are written as nested for blocks with separate integer iterators.
9. The Generate Loop Pattern (with genvar)
For synthesisable structural replication, the iterator is genvar, not integer:
`default_nettype none
module byte_register_array #(parameter N = 8) (
input wire clk,
input wire rst_n,
input wire [N-1:0] wr_en, // per-byte write enable
input wire [7:0] wr_data,
output wire [N-1:0][7:0] q_array // packed array of N × 8-bit
);
genvar i; // strictly compile-time
generate
for (i = 0; i < N; i = i + 1) begin : g_byte
byte_register u_byte (
.clk (clk),
.rst_n (rst_n),
.wr_en (wr_en[i]),
.wr_data(wr_data),
.q (q_array[i])
);
end
endgenerate
endmoduleThe genvar is consumed entirely by the elaborator — by the time simulation starts, i no longer exists; what exists is N copies of the byte_register instance, each connected to wr_en[<literal>], q_array[<literal>], etc. There is no runtime variable.
Using integer instead of genvar for a generate loop is legal in some flows (most simulators accept it) but flagged by lint (use genvar for generate iteration). The Chapter 14.7 generate-block sub-pages cover this in detail.
10. Simulation Behavior
An integer is stored as 32 bits of 4-state data in the simulator's memory. Assignments, reads, and operations behave identically to reg signed [31:0]. The simulator's scheduling rules from §10 of the 5.2.1 reg page apply unchanged:
- Blocking
=runs in Active region; immediate update. - Non-blocking
<=runs in Active region for the RHS, NBA region for the write.
There is no integer-specific simulator behaviour; the keyword is just a shorter way to declare a 32-bit signed reg.
11. Waveform Analysis
A waveform showing the sign-extension trap visually — same logical counter declared as integer and as reg [4:0], both incrementing through -1 (or its unsigned-wrap equivalent).
integer cnt vs reg [4:0] cnt — sign-extension behaviour
12 cyclesThe waveform illustrates the functional equivalence for the common case (positive counting). The sign-extension surprises only appear when the integer's value goes negative — which, for a well-designed RTL counter that never decrements past zero, doesn't happen. For testbenches that genuinely use signed arithmetic (e.g., computing expected ALU results that include sign), the integer semantic is correct.
12. Synthesis Behavior
integer is synthesisable. The synthesis tool maps an integer declaration to a 32-bit signed register, with the same gates as reg signed [31:0].
Three synthesis nuances:
- Constant-propagation may shrink the register. If the synthesis tool can prove (e.g., from constant initial values and bounded increments) that the integer only uses N bits, it may optimise the register down to N flops. This optimisation is not guaranteed — it depends on the tool, the constraints, and the always-block context.
- Sign extension on assignments. When an integer's value is assigned to a wider signed reg, the high bits sign-extend (replicate the integer's bit 31). When assigned to an unsigned wider reg, the high bits zero-extend.
- Comparisons follow the signed rules.
integer x; reg [7:0] y; if (x > y)performs a signed comparison after Verilog promotesyto 32-bit. Forx = -1, the comparison is(-1) > 0— TRUE ifyis non-negative when interpreted as signed.
The synthesis tool may emit lint warnings on integer use in synthesisable RTL (integer is not the recommended type for this signal — use reg [N-1:0] with explicit width). Production codebases typically allow integer only in generate blocks (as iterator surrogates for genvar) and explicitly forbid it elsewhere.
13. Industry Use Cases
Three patterns where integer is the right tool.
13.1 Testbench loop counters
Every Verilog testbench has at least one for loop in an initial block. The loop iterator is integer i;. Convention is to use single-letter names (i, j, k) for short loops and descriptive names (row, col, byte_idx) for longer loops or nested structures.
13.2 $display and $monitor argument indexing
System tasks like $display, $strobe, and $write take format strings. When iterating to print every element of a memory or every bit of a vector, the iterator is conventionally integer:
integer i;
initial begin
for (i = 0; i < 16; i = i + 1)
$display("cfg[%0d] = %h", i, cfg_reg[i]);
end13.3 Configuration scratch variables in testbench initial blocks
Testbenches frequently compute expected values, build stimulus matrices, or track scoreboarding state in integer variables. These are simulation-only constructs that never reach synthesis.
integer expected_sum;
integer error_count = 0;
initial begin
for (integer i = 0; i < 100; i = i + 1) begin
expected_sum = i * (i + 1) / 2;
// ... apply stimulus, sample DUT output, compare ...
if (dut_sum !== expected_sum) error_count = error_count + 1;
end
$display("Test complete. errors = %0d", error_count);
end14. Common Mistakes
Five pitfalls that catch engineers using integer.
14.1 Using integer for an unsigned counter
The §1 example pitfall. An 8-bit counter declared as integer consumes 32 flops, the upper 27 bits always zero. Worse, when the counter participates in comparisons or arithmetic with other unsigned values, the signed semantics produce surprises. Use reg [N-1:0] with the exact width.
14.2 Mixing integer and unsigned reg in arithmetic
Verilog's width-extension and signed-vs-unsigned rules (IEEE 1364-2005 §4.5) are precise but counter-intuitive. Mixing integer i and reg [7:0] addr in if (i == addr) triggers signed promotion, and the result for negative i is FALSE even when "the bits look the same." Always pick one type family per expression — either all signed or all unsigned — and use bit-slice or $signed() / $unsigned() for explicit conversion.
14.3 Using integer in a generate block instead of genvar
Legal in some flows but flagged by lint. The genvar keyword is the compile-time iterator; integer is a runtime variable. Even if the simulator accepts the code, the static-elaboration semantics belong to genvar.
14.4 Initial value at declaration in synthesisable RTL
integer cnt = 0; is legal but not synthesisable. The synthesis tool ignores the initial value; the silicon's reset path determines the actual power-on state. Reset the integer in the asynchronous-reset path of a clocked always block.
14.5 Forgetting that integer is 4-state
An integer can hold X and Z values, just like reg. At t = 0, every bit is X until the first procedural assignment. Reading an integer before initialising it propagates X through every downstream operator. The same discipline as for reg — initialise before reading — applies.
15. Debugging Lab
Three integer debug post-mortems
16. Coding Guidelines
- Use
integerfor testbench loop iteration. The canonical use; convention isinteger i;for short loops, descriptive names for longer ones. - Use
genvarfor generate-loop iteration, notinteger. The generate loop is compile-time;genvaris the right type. - Use
reg [N-1:0]for synthesisable counters, addresses, and data paths. Explicit width matches the actual range; no sign-extension surprises. - Use
reg signed [N-1:0]for synthesisable signed arithmetic. Explicit signed declaration is clearer thanintegerfor synthesisable code. - Don't mix
integerwith unsignedregin arithmetic or comparison. Pick one type family per expression. Use bit-slice or$signed()/$unsigned()for explicit conversion. - Bit-slice the iterator when assigning to a narrower target.
cfg_reg[i] = i[7:0];is clearer thancfg_reg[i] = i;(which produces a width-mismatch lint warning). - Use
%0dfor$displayof integers. Minimum width, no leading zeros.%dproduces 10-digit-wide output for 32-bit integers. - Reset every integer in synthesisable always blocks. Same discipline as
reg— initialise before reading.
17. Interview Q&A
18. Exercises
Three exercises that turn the variable-type choice into reflex.
Exercise 1 — Pick the right type
For each signal, recommend integer, reg [N-1:0], reg signed [N-1:0], or genvar.
| # | Signal description |
|---|---|
| 1 | A 16-cycle delay counter in synthesisable RTL |
| 2 | A loop iterator in a testbench initial block |
| 3 | A 32-bit signed accumulator in a DSP datapath |
| 4 | A generate loop iterator expanding N copies of a sub-module |
| 5 | A scoreboard error-count variable in a testbench |
| 6 | An 8-bit address pointer in an arbiter FSM |
Hints. Testbench iteration → integer. Generate iteration → genvar. Synthesisable signed math → reg signed [N-1:0]. Synthesisable unsigned counters/addresses → reg [N-1:0].
Exercise 2 — Spot the sign-extension trap
The following testbench fragment is supposed to iterate 256 times and check every byte value. Identify the bug and fix it.
integer i;
reg [7:0] data;
initial begin
for (i = 0; i < 256; i = i + 1) begin
data = i; // BUG: width mismatch
// ... apply data, sample DUT, compare ...
end
endWhat to produce. (a) Identify the width-mismatch warning the lint tool emits. (b) Rewrite the assignment using a bit slice to make the width explicit. (c) Explain why the bit-slice version is clearer about the engineer's intent.
Exercise 3 — Convert integer-based RTL to explicit-width
The following synthesisable counter uses integer. Rewrite it using explicit-width reg [N-1:0] and confirm the gate count drops.
module up_counter (
input wire clk,
input wire rst_n,
input wire en,
output wire max_reached
);
integer cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) cnt <= 0;
else if (en) cnt <= cnt + 1;
end
assign max_reached = (cnt == 255);
endmoduleWhat to produce. (a) Determine the exact bit width needed for cnt. (b) Rewrite using reg [N-1:0] cnt;. (c) Identify the gate-count reduction (number of flops saved).
19. Summary
integer is Verilog's convenience type for 32-bit signed variables. It is exactly equivalent to reg signed [31:0] for every purpose; the keyword is just shorter to type.
The properties baked into the keyword:
- Fixed 32-bit width — no range modifier allowed.
- Signed by default — two's complement semantics.
- 4-state value system — same as
reg, supportsXandZ. - Procedural-assignment target — assigned by
=or<=insidealways/initial. - Synthesisable — maps to 32 flops, with possible constant-propagation shrinkage.
The use cases:
- Loop iteration in testbenches. Canonical use;
integer i;forforloops. $display/$monitorargument indexing. Print every element of a memory.- Configuration scratch variables in testbench
initialblocks. Error counts, expected values, stimulus matrices.
The cases where integer is wrong:
- Synthesisable RTL counters, addresses, data paths, FSM states. Use
reg [N-1:0]with the exact width. generateblock iterators. Usegenvar(strictly compile-time).- Anywhere mixed-signedness arithmetic happens. The signed-vs-unsigned promotion rules produce surprises.
The day-to-day discipline:
integerfor testbench loops.genvarfor generate loops.reg [N-1:0]for everything else.- Don't mix
integerand unsignedregin arithmetic. Pick one type family per expression. - Use bit-slice for explicit width conversion.
i[7:0]is clearer than relying on width-extension. %0dfor$display. Minimum width, no leading zeros.- Reset every integer in synthesisable RTL. Same as
reg.
The next two sub-pages cover the rest of the variable family: 5.2.3 real (testbench-only floating-point) and 5.2.4 Scalar vs Vector vs Arrays (the dimensions every variable can take). After 5.2.4, Chapter 5 closes and Chapter 6 picks up with constant variables.
Related Tutorials
- reg — Chapter 5.2.1; the workhorse variable type that integer extends.
- Register Data Types — Chapter 5.2; the parent overview that motivates this sub-topic.
- Number Representation — Chapter 4.4; literals and sign-extension rules.
- Operator Usage — Chapter 4.3; the signed-vs-unsigned operator semantics that integer arithmetic uses.
- Variables & Data Types — Chapter 5; nets and variables together.