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 Foundations57
  • Combinational Design58
    • Combinational Timing Overview
    • Muxes, Decoders and Encoders
    • Arithmetic Blocks
    • Comparators and Shifters
    • Tri-State and Shared Buses
    • Latch Inference
    • Arithmetic Datapaths
      • Fixed-Point Contract
      • Width Growth
      • Rounding and Quantization
      • Saturation Policy
      • Divider Architectures
  • Sequential Design61
  • FSM & Control66
  • Timing & Clocking64
  • Reset & CDC58
  • Memory & Buses94
  • Advanced RTL60
  • Senior & Interview175
  • Glossary and Notation
Loading lesson

‹Arithmetic Datapaths
  1. Topics
  2. RTL Design
  3. Combinational Design
  4. Arithmetic Datapaths
  5. Rounding and Quantization

Part 2 · Combinational · Advanced

Rounding and Quantization

Control bias and error when discarding fractional or low-order bits.

On this page

    Why this matters

    Truncating by arithmetic shift is floor, not rounding toward zero: -1.25 becomes -2 while +1.25 becomes +1. Each such truncation errs in the same direction, so the errors add up as a bias.

    After this lesson you can: choose a rounding policy, handle negative ties, and implement a signed nearest-even quantizer from the floor quotient and remainder.

    Pick a policy for the bits you drop

    Dropping low bits loses information. The rounding policy decides which way each error goes, and whether many small errors add up to a bias. The running pipeline narrows its 42-bit accumulator only once, after accumulation, from 30 to 15 fraction bits.

    Policy

    Rule

    Bias/cost

    Truncate

    Discard low bits

    Cheap; biased toward negative infinity for arithmetic shift

    Round half up

    Add half LSB then truncate

    Simple; tie bias

    Round to nearest even

    Tie goes to even retained LSB

    Low aggregate bias; more logic

    Stochastic

    Randomized from discarded fraction

    Low long-run bias; needs random source

    Ties show the difference

    The examples below quantize to integer steps of 1 before any output-range clamp. Away from a tie, the three nearest policies agree. Arithmetic-shift truncation is floor, not truncation toward zero: a -1.25 input becomes -2, while +1.25 becomes +1.

    Four policies applied to the same four ties. Floor always moves down, half up always moves up, ties-away moves away from zero, and ties-even moves to the even neighbour.

    Read each line on its own: every dot is a tie, and its arrow lands on the integer that policy returns.

    • Floor and half up: every arrow points the same way, so on average the error has a sign. That is the bias.

    • Ties away: symmetric about zero, but -0.5 and +0.5 both move away, so magnitudes grow on average.

    • Ties even: -0.5 and +0.5 both go to 0, while -1.5 and +1.5 go outward. Half the ties move each way, so the bias cancels.

    Input

    Floor (arithmetic shift)

    Half up: floor(x+0.5)

    Nearest, ties away

    Nearest, ties even

    -2.5

    -3

    -2

    -3

    -2

    -1.5

    -2

    -1

    -2

    -2

    -0.5

    -1

    0

    -1

    0

    +0.5

    0

    1

    1

    0

    +1.5

    1

    2

    2

    2

    +2.5

    2

    3

    3

    2

    Signed rounding must handle negative ties deliberately. A reusable quantizer should document whether ties move away from zero, toward zero, or to even.

    Check yourself Why does adding a positive half LSB then arithmetic-shifting not implement ties away from zero?

    At -1.5 it produces floor(-1.5+0.5)=-1, toward zero. At +1.5 it produces +2, away from zero. That policy is half up, not symmetric ties-away.

    Worked example: two negative ties

    Take 8-bit signed codes with F = 2 fraction bits, so one step of the code is 1/4. The quantizer keeps the floor quotient q, looks at the dropped bits r, and adds 1 to q only when nearest-even says so.

    Step

    C = -6 (-1.5)

    C = -2 (-0.5)

    Stored bits

    1111_1010

    1111_1110

    q = C >>> 2 (floor)

    1111_1110 = -2

    1111_1111 = -1

    r = low 2 bits, unsigned

    10 = 2

    10 = 2

    Compare r with half (2)

    equal: a tie

    equal: a tie

    Is q odd (q[0] = 1)?

    no: keep q

    yes: add 1

    Result

    -2

    0

    Both inputs are ties, yet one stays and one moves up. Nearest-even looks only at the retained low bit q[0], never at the sign, which is why the same rule is unbiased on both sides of zero.

    Common pitfall

    SystemVerilog signed / truncates toward zero and signed % takes the sign of the dividend: -6 / 4 is -1 and -6 % 4 is -2. Neither gives the floor quotient and unsigned remainder above. Use an arithmetic shift for q and the raw low bits for r.

    Note

    Advanced: why the remainder is never negative. For signed integer code C with F discarded fractional bits, write C = q * 2^F + r, where q = floor(C / 2^F) and r = C - q * 2^F. Because q is the floor, 0 <= r < 2^F, even when C is negative. In two's complement, obtain q by arithmetic-shifting the signed code and interpret the discarded F low bits as an unsigned remainder.

    For F > 0, C / 2^F = q + r / 2^F. Nearest-even increments q when r > 2^(F-1), or when r == 2^(F-1) and q is odd; otherwise it keeps q. When F=0, there are no discarded bits and the code is unchanged. Widen and sign-extend q before adding the rounding increment so the result cannot wrap before the clamp.

    Check against the table: for C=-6 and F=2, q=-2 and r=-6 - (-2 * 4)=2. For C=-2, q=-1 and r=-2 - (-1 * 4)=2.

    Unsigned round half up with a clamp

    Unsigned values have no negative ties, so adding half an LSB and shifting is enough. The example below handles the no-fraction case separately so an otherwise legal parameter set never elaborates a negative shift. It keeps the rounded quotient wide until the range check, so narrow only after clamping as shown in Saturation policy.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // snippet: unsigned round-to-nearest, keep wide quotient for clamp
    logic [IW-1:0] value;
    logic [IW:0] quotient_wide;
    logic [OW-1:0] rounded;
    generate
      if ((F == 0) && (OW <= IW)) begin : g_no_fraction
        assign quotient_wide = value;
        assign rounded = (quotient_wide > {OW{1'b1}}) ? {OW{1'b1}} : quotient_wide[OW-1:0];
      end else if ((F > 0) && (F + OW <= IW + 1)) begin : g_round
        logic [IW:0] biased;
        assign biased  = {1'b0, value} + ({{IW{1'b0}}, 1'b1} << (F-1));
        assign quotient_wide = biased >> F;
        // With IW=8, F=2 and OW=6, value 255 rounds to 64 wide and clamps to 63 rather than wrapping to zero.
        assign rounded = (quotient_wide > {OW{1'b1}}) ? {OW{1'b1}} : quotient_wide[OW-1:0];
      end else begin : g_bad_quantizer_parameters
        initial $fatal(1, "Require (F == 0 && OW <= IW) or (F > 0 && F + OW <= IW + 1)");
      end
    endgenerate

    One signed nearest-even quantizer

    This combinational module implements the running policy. The signed quotient has an extra bit, the remainder is unsigned, and the signed clamp precedes narrowing. F=0 bypasses fractional rounding. Its flag means the rounded output code exceeded the representable range, not merely that low bits were discarded.

    signed_quantizer.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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    module signed_quantizer #(
      parameter int IW = 42, OW = 16, F = 15
    ) (
      input logic signed [IW-1:0] value_i,
      output logic signed [OW-1:0] value_o,
      output logic saturated_o
    );
      generate
        if (IW < 2 || OW < 2 || OW > IW + 1 || F < 0 || F >= IW) begin : g_bad
          $error("Require IW>=2, 2<=OW<=IW+1 and 0<=F<IW");
          assign value_o = '0;
          assign saturated_o = 1'b0;
        end else begin : g_valid
          localparam logic signed [IW:0] HI = $signed({1'b0, {(OW-1){1'b1}}});
          localparam logic signed [IW:0] LO = $signed({1'b1, {(OW-1){1'b0}}});
          logic signed [IW:0] floor_wide, rounded_wide;
          logic round_up;
          assign floor_wide = $signed({value_i[IW-1], value_i}) >>> F;
          if (F == 0) begin : g_integer
            assign round_up = 1'b0;
          end else begin : g_fraction
            localparam logic [F-1:0] HALF = {1'b1, {(F-1){1'b0}}};
            wire [F-1:0] remainder_bits = value_i[F-1:0];
            assign round_up = (remainder_bits > HALF) ||
                              ((remainder_bits == HALF) && floor_wide[0]);
          end
          assign rounded_wide = floor_wide + $signed({{IW{1'b0}}, round_up});
          always_comb begin
            saturated_o = 1'b0;
            if (rounded_wide > HI) begin
              value_o = HI[OW-1:0]; saturated_o = 1'b1;
            end else if (rounded_wide < LO) begin
              value_o = LO[OW-1:0]; saturated_o = 1'b1;
            end else value_o = rounded_wide[OW-1:0];
          end
        end
      endgenerate
    endmodule
    

    The small-width test checks every eight-bit signed code for F=0,1,2 against an independent distance search, including negative non-ties, both retained tie parities and rounding overflow. The precision-verification lesson reuses this exact module after the 42-bit MAC, with separate integer and real-valued references.

    • Quantize once at a deliberate boundary, not after every operator.

    • Keep guard bits through the high-dynamic-range portion of the datapath.

    • Measure maximum error, RMS error, bias, and saturation rate against realistic vectors.

    Check yourself Why can repeated truncation create a DC bias?

    Each truncation discards information in the same direction. Across many stages the signed errors do not cancel, shifting the average result.

    Remember

    • Write C = q * 2^F + r with q = floor(C / 2^F), so 0 <= r < 2^F even when C is negative.

    • Nearest-even increments q when r > 2^(F-1), or when r equals 2^(F-1) and q is odd.

    • Quantize once at a deliberate boundary and keep guard bits before it.

    Rule of thumb: document whether ties go away from zero, toward zero, or to even.

    Keep going

    • Interview: run a mock interview on this topic

    Related topics

    • Saturation policy

    Previous

    113 / 694

    Next: Saturation Policy

    Up next

    Up nextSaturation Policy

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Fixed-Point Contract
    • Divider Architectures