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

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
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
On this page

    Part 1 of 9, lesson 10 of 36Part 1 of 9/Foundations/Lesson 10 of 36Beginner

    Sign Extension, Zero Extension and Casting

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

    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

    $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.

    • 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.

    • 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 lessonPrevious lessonReport an error

    See also

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