FPGA

Verilog Shift and Conditional Operators

By CourseTron Team ·  3 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: >> and << always fill with zeros, >>> copies the sign bit only when the whole expression is signed, and ?: is a hardware multiplexer that merges both inputs bit by bit when its condition is X or Z.

    Why these three operators trip up interviewees

    Shifts and the conditional operator look trivial, yet they produce some of the most common silent bugs in RTL: a divide-by-four that gives the wrong answer for negative numbers, a barrel shifter that synthesizes to the wrong width, or a multiplexer that leaks X values into a testbench. All three bugs share one root cause. Verilog decides the signedness and the width of an expression from its context, not from the operator you typed. If you have not yet read how sign extension in Verilog works, skim it first, because every arithmetic shift is really a sign-extension question in disguise.

    Logical shifts: << and >>

    A logical shift moves every bit by the requested number of positions and fills the vacated positions with 0. The bits pushed past the edge of the result width are discarded. Take a = 8'b1001_0100:

    • a >> 2 gives 8'b0010_0101 (the two low bits 00 fall off, two zeros enter at the top).
    • a << 2 gives 8'b0101_0000 (the two high bits 10 are lost).

    For unsigned values, a left shift by n multiplies by 2n and a right shift divides by 2n, discarding the remainder. In hardware a constant shift costs nothing: it is pure wiring, so synthesis tools such as Synopsys Design Compiler, Cadence Genus and Vivado simply reconnect nets. Only a variable shift amount creates logic, namely a barrel shifter built from log2(N) layers of 2:1 multiplexers.

    Same input, shifted right by 2: what fills the top? a = 8'b10010100 1 0 0 1 0 1 0 0 a >> 2 (logical) = 8'b00100101 = 37 0 0 1 0 0 1 0 1 $signed(a) >>> 2 = 8'b11100101 = -27 1 1 1 0 0 1 0 1 zero fill (orange) sign copy (green) CourseTron · coursetron.com
    The arithmetic shift preserves the sign: -108 becomes -27, exactly -108 / 4. The logical shift turns it into +37.

    Arithmetic shifts: <<< and >>>

    Arithmetic left shift <<< is identical to <<: zeros enter from the right, because shifting left never needs to protect the sign in two's complement. The real difference is the right shift. >>> replicates the most significant bit, but only if the operand is signed. If the expression is unsigned, >>> behaves exactly like >>. That conditional is the single most-asked Verilog shift interview question.

    Arithmetic right shift is a floor division by 2n, which rounds toward negative infinity. For -5 (8'b11111011), >>> 1 yields 8'b11111101, which is -3, whereas C-style division gives -2. Remember this when you build fixed-point filters, because the bias of -0.5 LSB accumulates over many taps.

    Key fact: >>> sign-extends only when every operand in the expression is signed. One unsigned operand silently turns the whole expression unsigned.

    The signedness trap, with code

    reg signed [7:0] s = 8'sb1001_0100;   // -108
    reg        [7:0] u = 8'b1001_0100;    // 148
    
    wire signed [7:0] r1 = s >>> 2;         // 8'b1110_0101 = -27  (correct)
    wire        [7:0] r2 = u >>> 2;         // 8'b0010_0101 = 37   (unsigned: zero fill)
    wire signed [7:0] r3 = $signed(u) >>> 2; // -27, cast makes it signed
    wire signed [7:0] r4 = s >>> 2'd2 + u;    // BUG: see below

    The shift amount on the right is always treated as an unsigned, self-determined value, so it never contaminates the left operand. The trap appears when you write something like (s + u) >>> 1. Because u is unsigned, the addition is unsigned, the sum is zero-extended, and >>> degrades to >>. Lint tools such as Synopsys SpyGlass, Cadence HAL and Siemens Questa AutoCheck all flag signed/unsigned mixing for exactly this reason. The cure is explicit: cast with $signed(), or declare the operands signed from the start. Our guide to the core Verilog data types explains how the signed keyword works on reg and wire alike, and the page on reg vs wire covers the declaration side.

    Width, overshift and edge cases

    ExpressionResult (8-bit)Why
    8'hF0 >> 88'h00Shifting by the full width or more clears every bit
    8'sh80 >>> 98'hFFSigned overshift leaves only copies of the sign bit
    4'b1000 << 1 in a 4-bit context4'b0000The carried-out bit is truncated; widen the target to keep it
    a >> 2'bx1all XAn X in the shift amount makes the whole result X

    A frequent bug is shifting inside a context that is too narrow. In wire [15:0] y = a << 4; with an 8-bit a, Verilog first extends a to 16 bits because the assignment context is the widest operand, so the shift keeps its carried bits. In a comparison or concatenation the context differs, so check the widths with your simulator's lint pass rather than trusting intuition.

    The conditional operator ?:

    y = sel ? a : b; describes a 2:1 multiplexer. It is an expression, so it can sit inside a continuous assign, which is why it is the idiomatic tool for small datapath selection. Chains such as s1 ? a : s0 ? b : c create a priority structure, while a case statement usually maps to a flat parallel mux. Synthesis tools often optimize both well, but write the structure you actually intend.

    y = sel ? a : b is a 2:1 multiplexer 1 0 MUX a b y sel If sel is X or Z: a = 1010 b = 1100 y = 1xx0 Equal bits survive, differing bits become X. CourseTron · coursetron.com
    When the select is unknown, the simulator merges both data inputs bit by bit instead of picking one.

    That X-merge rule is more optimistic than an if/else statement. An if (sel) treats an X condition as false and silently takes the else branch, hiding the problem, whereas ?: propagates the uncertainty only into the bits that actually differ. Gate-level simulations and X-propagation checks in tools like Siemens Questa and Cadence Xcelium rely on that distinction. Also note that the result width is the larger of the two branches, and the result is signed only if both branches are signed, the same contamination rule that affects shifts. If you assign registers on a clock edge, pair this with the rules in blocking vs non-blocking assignments.

    Key fact: a known select gives a plain mux; an unknown select gives a per-bit merge, never a coin-flip. Equal bits stay known.

    Watching a signed shift in motion

    Animated: s >>> 2 slides bits right, sign bit refills the left before 1 0 0 1 0 1 0 0 after 1 1 1 0 0 1 0 1 0 0 Green = copies of the sign bit entering. Pink = low bits being discarded. Final value 11100101 = -27 = -108 / 4. CourseTron · coursetron.com
    The window is the 8-bit register. Two sign copies slide in from the left while the two lowest bits are pushed out.

    Practical guidance for FPGA and ASIC work

    • Use constant shifts for scaling by powers of two; they are free wires. On Xilinx and Intel devices, a variable shift of a wide bus consumes LUT layers, so register the stages if timing is tight.
    • If you are shifting data through time rather than across bits, a chain of flip-flops is a different structure: see FPGA SRL shift-register LUTs for how it packs into one LUT.
    • When shifted values feed an adder, understand how the result maps onto FPGA carry chains before you chase timing.
    • Always declare the signedness of fixed-point signals explicitly, and run a lint step in your flow.

    If you want mentored practice on these RTL idioms with real tool flows, browse the CourseTron courses, or explore hands-on VLSI training from our sister brand ChipXpert.

    Ready to write interview-grade RTL? Join the Verilog and FPGA track in the CourseTron course catalogue and practise every operator in this article on live labs.

    Frequently asked questions

    What is the difference between >> and >>> in Verilog?

    >> always inserts zeros on the left. >>> inserts copies of the sign bit, but only when the expression is signed; for unsigned operands it also inserts zeros.

    Is <<< different from <

    No. Both shift left and fill the low bits with zeros, so the two operators give identical results.

    Why does my >>> give a positive number for a negative value?

    Almost certainly because another operand in the expression is unsigned, which makes the whole expression unsigned. Declare the signals signed or wrap them in $signed().

    What does the conditional operator return when the condition is X?

    It compares both branches bit by bit: bits that agree keep their value and bits that differ become X. An if statement would instead take the else branch.

    Advertisement