Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
    • Digital Logic for RTL Engineers
    • Number Systems for RTL Engineers
      • Representation Drives Behavior
      • Binary and Hexadecimal Conversion Habits
      • Two's Complement Intuition
      • Sign Extension, Zero Extension and Casting
      • SystemVerilog Signed Arithmetic Pattern
      • Overflow Detection Patterns
    • Verilog Fundamentals
    • Verilog Operators and Types
    • SystemVerilog for RTL
    • Simulation and Testbenches
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Number Systems for RTL Engineers
  1. Topics
  2. RTL Design
  3. RTL Foundations
  4. Number Systems for RTL Engineers
  5. Sign Extension, Zero Extension and Casting

Part 1 · Foundations · Beginner

Sign Extension, Zero Extension and Casting

Zero-extend unsigned values, sign-extend signed values, and cast mixed expressions.

On this page

    Why this matters

    Adding the 4-bit signed value -8 to an unsigned 8-bit 1 gives 9, not -7, because the unsigned operand makes the sum unsigned. This is one of the most common signed-arithmetic bugs, and it simulates without a warning.

    After this lesson you can: extend a value with the right fill bits, and write casts so that mixed signed and unsigned operands give the intended result.

    Extending a value

    Widening an N-bit value means choosing the new upper bits. The choice must preserve the value.

    • Zero extension fills with 0. Correct for unsigned values.

    • Sign extension copies the top bit into every new bit. Correct for two's complement values (−5 stays −5).

    extend_demo.v
    1
    2
    3
    4
    5
    6
    7
    8
    module extend_demo (
      input  wire [3:0]  a,
      output wire [7:0]  zx,
      output wire [7:0]  sx
    );
      assign zx = {4'b0000, a};             // zero extension
      assign sx = {{4{a[3]}}, a};           // sign extension by replication
    endmodule

    a (4-bit)

    zero-extended (8-bit)

    sign-extended (8-bit)

    Signed meaning

    0101

    0000_0101

    0000_0101

    +5 (both agree)

    1011

    0000_1011 = 11

    1111_1011

    −5 only the sign-extended one is still −5

    Bits and signed types are separate

    Replication builds the right bits but a concatenation is unsigned. The sx port above therefore holds unsigned 251 for 8'hFB; it means -5 only when interpreted as signed. A signed declaration or cast makes that interpretation explicit before a comparison or further widening.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    module tb_extension_meaning;
      logic [3:0] a = 4'b1011;
      logic [7:0] raw;
      logic signed [7:0] signed_copy;
      logic signed [8:0] widened;
      initial begin
        raw = {{4{a[3]}}, a};
        signed_copy = $signed(raw);
        widened = signed_copy;                   // sign-extends the signed source
        if (raw !== 8'd251 || signed_copy !== -8'sd5 || widened !== -9'sd5)
          $fatal(1, "FAIL: sign-extended bits and signed interpretation differ");
        if (raw < 8'd0 || !(signed_copy < 8'sd0))
          $fatal(1, "FAIL: signed comparison did not use the declared interpretation");
        $display("PASS: FB is unsigned 251 or signed -5; signed widening keeps -5");
        $finish;
      end
    endmodule

    Mixed expressions turn unsigned

    Verilog types an expression from all its operands together. If any operand is unsigned, the result is unsigned.

    Add the 4-bit signed value −8 to the unsigned 8-bit value 1. The experiment below shows the rule in action.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    module tb_typing;
      logic [3:0]        a = 4'b1000;      // -8 if read as signed
      logic [7:0]        b = 8'd1;         // unsigned
      logic signed [7:0] r_bad, r_good, r_explicit;
      int errors = 0;
    
      initial begin
        r_bad      = $signed(a) + b;                 // b is unsigned -> whole expression unsigned
        r_good     = $signed(a) + $signed(b);        // fine only because b = 1 is below 128 (see the next example)
        r_explicit = {{4{a[3]}}, a} + b;             // explicit sign extension, then add
    
        $display("bad=%0d  good=%0d  explicit=%0d", r_bad, r_good, r_explicit);
        if (r_bad      !==  8'sd9) errors++;         // -8 treated as 8: 8 + 1 = 9  (the trap)
        if (r_good     !== -8'sd7) errors++;         // -8 + 1 = -7
        if (r_explicit !== -8'sd7) errors++;
        if (errors == 0) $display("PASS: confirmed the unsigned-contamination rule");
        $finish;
      end
    endmodule

    Common mistakes

    Common pitfall

    $signed(a) + b looks like signed arithmetic but is unsigned because b is. The 4-bit $signed(a) is zero-extended, so −8 turns into +8 and the answer is 9, not −7. This is a very common bug.

    Four habits avoid the trap:

    1. Declare numeric signals signed where they are declared, not at the point of use.

    2. Cast both operands with $signed(a) + $signed(b) only when both really are signed values. A cast changes interpretation, not the bits, so an unsigned 8-bit 200 becomes −56.

    3. To mix a signed operand with an unsigned one, zero-extend the unsigned one into a wider signed type first, for example $signed({1'b0, b}) or a wider signed copy. Once the whole expression is signed, the signed operand is sign-extended automatically.

    4. Or extend explicitly with replication, so the code shows exactly what happens to the upper bits.

    The cast-both habit goes wrong for an upper-half unsigned value. Add the signed 4-bit −8 to the unsigned 8-bit value 200: the correct sum is 192, but $signed(a) + $signed(b) reads b as −56 and gives −64. Zero-extending b first gives 192.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    module tb_cast_unsigned;
      logic signed [3:0] a = -4'sd8;
      logic        [7:0] b = 8'd200;           // upper-half unsigned value
      logic signed [9:0] r_wrong, r_right;
      int errors = 0;
    
      initial begin
        r_wrong = $signed(a) + $signed(b);            // b is reinterpreted: 200 -> -56
        r_right = $signed(a) + $signed({1'b0, b});    // zero-extend first: stays +200
    
        $display("wrong=%0d  right=%0d", r_wrong, r_right);
        if (r_wrong !== -10'sd64) errors++;           // -8 + (-56) = -64
        if (r_right !==  10'sd192) errors++;          // -8 + 200  = 192
        if (errors == 0) $display("PASS: zero-extend the unsigned operand before mixing");
        else $display("FAIL: wrong=%0d right=%0d", r_wrong, r_right);
        $finish;
      end
    endmodule

    In SystemVerilog, a size cast such as 8'(x) changes width while preserving the operand's signedness. Cast order therefore depends on the intended interpretation: to widen a raw two's-complement pattern, interpret it as signed first, then widen. For unsigned logic [3:0] x = 4'h8, signed'(8'(x)) zero-extends first and gives +8, while 8'($signed(x)) sign-extends and gives -8. If the pattern really represents unsigned 8, widening first preserves that meaning.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    module tb_cast_order;
      logic [3:0] x;
      logic signed [7:0] widen_first, sign_first;
      int expected_unsigned, expected_signed, errors = 0;
    
      initial begin
        x = 4'h8;
        widen_first = $signed(8'(x));
        sign_first  = 8'($signed(x));
        if (widen_first !== 8'sd8 || sign_first !== -8'sd8) errors++;
    
        for (int i = 0; i < 16; i++) begin
          x = i[3:0];
          widen_first = $signed(8'(x));
          sign_first  = 8'($signed(x));
          expected_unsigned = i;
          expected_signed = (i < 8) ? i : i - 16;
          if (widen_first !== expected_unsigned[7:0]) errors++;
          if (sign_first  !== expected_signed[7:0]) errors++;
        end
        if (errors == 0) $display("PASS: cast order preserves the chosen interpretation for all 16 patterns");
        else $display("FAIL: cast-order errors=%0d", errors);
        $finish;
      end
    endmodule
    Check yourself A 12-bit signed sample must be added to a 16-bit signed accumulator. Write the extension of the sample.

    {{4{sample[11]}}, sample} replicates the sign bit four times to reach 16 bits. If both are declared signed, plain acc + sample does the same extension automatically.

    Remember

    • Zero extension is correct for unsigned values and sign extension for two's complement values.

    • A concatenation is unsigned even when it copies the sign bit.

    • A cast changes interpretation, not bits. To mix operands, zero-extend the unsigned one into a wider signed type first.

    Rule of thumb: widen first with the fill you mean, then do the arithmetic in one signedness.

    Keep going

    • Code lab: Build a signed comparator

    • Interview: run a mock interview on this topic

    Related topics

    • SystemVerilog signed arithmetic pattern

    Previous

    10 / 350

    Next: SystemVerilog Signed Arithmetic Pattern

    Up next

    Up nextSystemVerilog Signed Arithmetic Pattern

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Representation Drives Behavior
    • Binary and Hexadecimal Conversion Habits
    • Overflow Detection Patterns