Operators and Expressions
Shift Operators in Verilog
Logical (<<, >>) vs. arithmetic (<<<, >>>) shifts in Verilog, and why a naive >> on a signed value loses the sign bit instead of preserving it.
3 min read · Updated
<< and >> are logical shifts: they move every bit left or right by the given count and fill the vacated positions with 0, regardless of what the original value meant. <<< and >>> are arithmetic shifts: left arithmetic shift behaves identically to logical left shift, but right arithmetic shift fills the vacated high bits by replicating the original sign bit instead of filling with 0 — correct only when the operand is declared signed.
Hardware it becomes depends on the shift amount. A shift by a fixed, compile-time constant (a << 2) is mostly rewiring — each output bit is simply connected to a different input bit, with 0 wired to the vacated positions; no gates compute anything. A shift by a runtime-variable amount (a << n, where n is a signal) is a small selection network — commonly called a barrel shifter — built from a cascade of muxes, one stage per bit of the shift amount, and genuinely costs hardware.
Predict before running
neg = -8, declared signed. Before running it, predict whether neg >> 1 and neg >>> 1 produce the same bit pattern, and what each one means as a signed number.
module shift_left (
input wire [7:0] a,
input wire [2:0] n,
output wire [7:0] y
);
assign y = a << n;
endmodule
module tb;
reg [7:0] a;
reg [2:0] n;
wire [7:0] y;
reg signed [7:0] neg;
shift_left u_shift (.a(a), .n(n), .y(y));
initial begin
a = 8'b0000_0011;
n = 3'd2;
#1;
$display("a=%b << %0d = %b (logical left shift, zero-fills from the right)", a, n, y);
neg = -8;
$display("neg=%b (%0d)", neg, neg);
$display("neg >> 1 = %b (%0d) -- logical shift, zero-fills from the left", neg >> 1, $signed(neg >> 1));
$display("neg >>> 1 = %b (%0d) -- arithmetic shift, replicates the sign bit", neg >>> 1, neg >>> 1);
$display("PASS");
$finish;
end
endmoduleExpected output — reveal after you predict
a=00000011 << 2 = 00001100 (logical left shift, zero-fills from the right)
neg=11111000 (-8)
neg >> 1 = 01111100 (124) -- logical shift, zero-fills from the left
neg >>> 1 = 11111100 (-4) -- arithmetic shift, replicates the sign bit
PASS-8 >>> 1 produces -4, which is the mathematically correct result of dividing -8 by 2 and rounding toward negative infinity — the arithmetic shift preserves the value's sign by replicating the original top bit (1) into the vacated position. -8 >> 1 produces 124 — a positive number, because the logical shift fills with 0 regardless of the operand's declared signedness, corrupting the sign entirely.
Common mistakes
- Using
>>on a signed value expecting sign preservation.>>always zero-fills; only>>>replicates the sign bit, and only when the operand is genuinelysigned— a plainwire/regtreated as signed only inside a$signed()cast still shifts logically unless the shift itself is>>>. - Assuming
<<<differs from<<. Arithmetic and logical left shift are identical (both zero-fill from the right); the distinction between arithmetic and logical shifting only matters on the right shift. - Shifting by a variable amount without checking its range. A shift count wider than the operand (or negative, if it comes from a signed variable) is legal syntax but produces a result that is easy to reason about incorrectly — keep shift-amount widths deliberately narrow and documented.
Sources and verification
Example provenance: SkillLift Labs authored the example for this tutorial. tests/scripts/tutorial-verified-examples.test.ts extracts the complete marked example from this Markdown file, compiles it in Verilog-2005 mode with Icarus Verilog, runs it, and requires the simulator output to match the Expected output block exactly. The same test also carries an independent, code-owned semantic oracle, and exercises the article-to-runner source-splitting path. Automation verifies those stated properties; human technical review for indexing remains a separate gate.