Saturating Adder in Verilog: Clamp on Overflow with Code
A saturating adder is an adder that, when the true sum exceeds the range of the output word, clamps the result to the largest (or smallest) representable value instead of letting it wrap around modulo 2N. It is the default accumulation style in audio and DSP datapaths, where a wrapped sample produces a loud click but a clamped sample just distorts gracefully. This page gives you clean, synthesizable saturating adder Verilog code for both unsigned and signed operands, each wrapped in a self-checking testbench.
The two examples below are plain Verilog-2005. Example 1 is a parameterized unsigned clamping adder (overflow detected from the carry-out). Example 2 is a signed clamping adder that clamps positive overflow to +MAX and negative overflow to −MIN, using the classic sign-bit overflow rule. Both are checked against a golden reference computed in wider integer arithmetic.
An unsigned WIDTH-bit saturating adder. We add the operands in a one-bit-wider sum so the carry-out is visible, and when it is set we force the output to all-ones (2N−1). The testbench sweeps operand pairs and compares against min(a+b, 2^N-1).
module sat_adder_unsigned #(parameter WIDTH = 8) (
input wire [WIDTH-1:0] a,
input wire [WIDTH-1:0] b,
output wire [WIDTH-1:0] sum
);
wire [WIDTH:0] raw = {1'b0, a} + {1'b0, b};
assign sum = raw[WIDTH] ? {WIDTH{1'b1}} : raw[WIDTH-1:0];
endmodule
module tb;
localparam WIDTH = 8;
reg [WIDTH-1:0] a, b;
wire [WIDTH-1:0] sum;
integer i, j;
integer wide;
reg [WIDTH-1:0] golden;
integer errors;
sat_adder_unsigned #(.WIDTH(WIDTH)) dut (.a(a), .b(b), .sum(sum));
initial begin
errors = 0;
// sweep a representative set of operand pairs (steps of 17 to stay fast)
for (i = 0; i < (1<<WIDTH); i = i + 17) begin
for (j = 0; j < (1<<WIDTH); j = j + 23) begin
a = i[WIDTH-1:0];
b = j[WIDTH-1:0];
#1;
wide = i + j; // true sum in integer math
if (wide > ((1<<WIDTH)-1))
golden = {WIDTH{1'b1}}; // clamp to 2^N-1
else
golden = wide[WIDTH-1:0];
if (sum !== golden) begin
$display("FAIL a=%0d b=%0d sum=%0d expected=%0d", a, b, sum, golden);
errors = errors + 1;
end
end
end
// explicit corner cases
a = 8'd200; b = 8'd100; #1; // 300 -> clamp 255
if (sum === 8'd255) $display("PASS overflow 200+100 clamped to %0d", sum);
else begin $display("FAIL overflow case got %0d", sum); errors = errors + 1; end
a = 8'd255; b = 8'd1; #1; // 256 -> clamp 255
if (sum === 8'd255) $display("PASS overflow 255+1 clamped to %0d", sum);
else begin $display("FAIL 255+1 got %0d", sum); errors = errors + 1; end
a = 8'd100; b = 8'd55; #1; // 155 in range
if (sum === 8'd155) $display("PASS in-range 100+55 = %0d", sum);
else begin $display("FAIL in-range got %0d", sum); errors = errors + 1; end
if (errors == 0) $display("ALL TESTS PASSED");
else $display("%0d FAILURES", errors);
$finish;
end
endmodule
PASS overflow 200+100 clamped to 255 PASS overflow 255+1 clamped to 255 PASS in-range 100+55 = 155 ALL TESTS PASSEDThe log shows a full operand sweep running silently (no FAIL lines), then three explicit corner cases: 200+100 and 255+1 both clamp to 255 instead of wrapping to 44 and 0, and the in-range 100+55 passes through as 155.
ALL TESTS PASSEDconfirms the hardware matched the widenedmin(a+b, 255)reference on every vector.A signed two-sided clamping adder. Overflow cannot be read from a single carry bit in two's complement, so we detect it from the sign bits: two positives producing a negative is positive overflow (clamp to +MAX), two negatives producing a positive is negative overflow (clamp to −MIN). The testbench sweeps the full signed range and checks against a clamped integer reference.
module sat_adder_signed #(parameter WIDTH = 8) ( input wire signed [WIDTH-1:0] a, input wire signed [WIDTH-1:0] b, output reg signed [WIDTH-1:0] sum ); wire signed [WIDTH:0] raw = {a[WIDTH-1], a} + {b[WIDTH-1], b}; localparam signed [WIDTH-1:0] SMAX = {1'b0, {(WIDTH-1){1'b1}}}; // 2^(N-1)-1 localparam signed [WIDTH-1:0] SMIN = {1'b1, {(WIDTH-1){1'b0}}}; // -2^(N-1) always @(*) begin // overflow when operands share sign but result sign differs if (!a[WIDTH-1] && !b[WIDTH-1] && raw[WIDTH-1]) sum = SMAX; // positive overflow else if (a[WIDTH-1] && b[WIDTH-1] && !raw[WIDTH-1]) sum = SMIN; // negative overflow else sum = raw[WIDTH-1:0]; end endmodule module tb; localparam WIDTH = 8; reg signed [WIDTH-1:0] a, b; wire signed [WIDTH-1:0] sum; integer i, j; integer wide, lo, hi; reg signed [WIDTH-1:0] golden; integer errors; sat_adder_signed #(.WIDTH(WIDTH)) dut (.a(a), .b(b), .sum(sum)); initial begin errors = 0; lo = -(1<<(WIDTH-1)); // -128 hi = (1<<(WIDTH-1)) - 1; // 127 for (i = lo; i <= hi; i = i + 13) begin for (j = lo; j <= hi; j = j + 11) begin a = i[WIDTH-1:0]; b = j[WIDTH-1:0]; #1; wide = i + j; if (wide > hi) golden = hi[WIDTH-1:0]; else if (wide < lo) golden = lo[WIDTH-1:0]; else golden = wide[WIDTH-1:0]; if (sum !== golden) begin $display("FAIL a=%0d b=%0d sum=%0d expected=%0d", a, b, sum, golden); errors = errors + 1; end end end // explicit corners a = 8'sd100; b = 8'sd100; #1; // 200 -> +127 if (sum === 8'sd127) $display("PASS pos-overflow 100+100 clamped to %0d", sum); else begin $display("FAIL pos-overflow got %0d", sum); errors = errors + 1; end a = -8'sd100; b = -8'sd100; #1; // -200 -> -128 if (sum === -8'sd128) $display("PASS neg-overflow -100-100 clamped to %0d", sum); else begin $display("FAIL neg-overflow got %0d", sum); errors = errors + 1; end a = 8'sd50; b = -8'sd20; #1; // 30 in range if (sum === 8'sd30) $display("PASS in-range 50+(-20) = %0d", sum); else begin $display("FAIL in-range got %0d", sum); errors = errors + 1; end if (errors == 0) $display("ALL TESTS PASSED"); else $display("%0d FAILURES", errors); $finish; end endmodulePASS pos-overflow 100+100 clamped to 127
PASS neg-overflow -100-100 clamped to -128
PASS in-range 50+(-20) = 30
ALL TESTS PASSEDThe log sweeps the whole signed range with no FAIL lines, then shows the two-sided clamp in action: 100+100 pins to +127 (not −56 as wrapping would give), −100−100 pins to −128 (not +56), and 50+(−20) passes through as 30. Because the reference clamps to [−128, 127] independently, a one-sided or wrong-polarity clamp would print FAIL immediately.
Wrap vs. saturate: why clamping matters
An ordinary N-bit adder is a modulo-2N machine. When the true sum does not fit, the result wraps: in 8-bit unsigned arithmetic 255 + 1 becomes 0, and in 8-bit signed arithmetic 100 + 100 becomes −56. For address counters or hash mixing that wrap-around is exactly what you want. For a signal value it is a catastrophe: a sample that should have been a little too loud instead flips to the opposite extreme, producing a full-scale discontinuity.
A saturating adder replaces the wrap with a clamp. Once the result reaches the top of the range it sticks at the maximum; once it reaches the bottom it sticks at the minimum. The transfer curve is linear in the middle and flat at both ends, which is why saturation is sometimes called “clipping.” The cost is one comparison (or a couple of gates on the sign/carry bits) and a mux in front of the output.
How overflow is detected
Unsigned
For unsigned operands overflow is trivial: extend each operand by one zero bit, add, and look at the extra MSB (the carry-out). If it is 1 the sum did not fit, and the only possible saturated value is all-ones, 2N−1. There is no underflow for unsigned addition, so the clamp is one-sided.
Signed (two's complement)
For signed operands the carry-out alone does not indicate overflow. The correct rule uses operand and result signs: overflow occurs only when both operands have the same sign and the result has the opposite sign. Concretely, positive + positive → negative is a positive overflow (clamp to +MAX = 2N-1−1), and negative + negative → positive is a negative overflow (clamp to −MIN = −2N-1). Adding two operands of opposite sign can never overflow, so those cases always pass through.
In the signed example we add in a width+1 sign-extended sum so the true result sign (
raw[WIDTH-1]of the extended value) is reliable even at the boundaries, then apply the two sign-bit tests to pick +MAX, −MIN, or the unmodified low bits.Where saturation is used
- Audio DSP — accumulators in filters, mixers, and volume stages saturate so that an overdriven signal clips (soft audible distortion) rather than wraps (a harsh click or full-scale glitch).
- Fixed-point DSP cores — most DSP ISAs provide saturating add/subtract and saturating MAC instructions; the hardware here is the datapath behind them.
- Image and video pipelines — pixel math clamps to [0, 255] so brightening a bright pixel keeps it white instead of turning it black.
- Control and sensor fusion — integrators use saturation for anti-windup, holding a bounded value when the input would push past the limit.
In all of these the saturating adder is the primitive: a normal adder plus overflow detection plus a clamp mux.
Common mistakes
- Using the carry-out for signed overflow. The unsigned carry bit does not detect two's-complement overflow. Use the sign-bit rule, or XOR the carry-in and carry-out of the MSB.
- Clamping to the wrong value. Positive overflow must clamp to +MAX, negative overflow to −MIN. Mixing these (or clamping both sides to the same value) is a classic bug the two-sided golden check in Example 2 catches instantly.
- Forgetting the asymmetry of signed range. For N bits the signed range is [−2N-1, 2N-1−1] — one more negative value than positive. +MAX and −MIN are not mirror images.
- Not widening before you add. If you detect overflow on a truncated N-bit sum you have already lost the carry; add in N+1 bits (or keep the carry-out) first.
- Reusing an unsigned clamp for signed data. They are different circuits with different detection and a different clamp target.
Synthesis notes and FAQ
Both designs are purely combinational and fully synthesizable. They map to one adder plus a small amount of logic: for the unsigned version, a carry-out and a WIDTH-bit mux to all-ones; for the signed version, two AND/NOT sign tests and a mux selecting +MAX, −MIN, or the raw low bits. Timing is dominated by the adder's carry chain, with one extra mux delay on the output.
Is a saturating adder slower than a plain adder?
Only marginally. The adder itself is unchanged; saturation adds the overflow logic and an output mux, roughly one gate layer. If it sits on your critical path, register the raw sum and do the clamp in the next stage.
How do I make it pipelined or registered?
Change the output
always @(*)toalways @(posedge clk), or add a pipeline register after the combinational clamp. The logic is identical; only the timing changes.Does it scale to any width?
Yes — both modules are parameterized by
WIDTH. Change the parameter and the SMAX/SMIN constants and the all-ones clamp track it automatically. The testbenches use WIDTH = 8 for a fast, exhaustive-enough sweep.What about saturating subtraction?
Same idea: for signed subtraction the overflow rule flips (operands of opposite sign can overflow); for unsigned subtraction you clamp the borrow case to 0 instead of all-ones.
Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.▶ Try it in the ChipXpert LabRun it in the LabOpen this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.
Learn Verilog with mentors, labs and placement support
This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.
Explore VLSI Courses Hi! Ask me about courses, fees, batches or discounts. ×ChipXpert AssistantAsk about courses, fees, batches and centres