Floating-Point Adder in Verilog: IEEE-754 Add with Code
A floating-point adder takes two IEEE-754 numbers and produces their sum in the same format. Unlike integer addition, the operands first have to be lined up by exponent, then added or subtracted by sign, and the result has to be re-normalized and repacked. This page gives you a complete, synthesizable floating point adder Verilog code for 32-bit single precision, with testbenches that self-check against known-answer vectors.
To keep every result exact and verifiable, the design is scoped to normal numbers and zero and uses round toward zero (truncation). That is enough to teach the full datapath: field unpacking, the hidden leading 1, exponent-difference alignment, sign-based add/subtract, and leading-zero normalization. Denormals, NaN/Inf, and round-to-nearest are discussed at the end as what full IEEE-754 compliance would add.
A single combinational FP32 adder. It unpacks both operands, aligns the smaller significand by the exponent difference, adds or subtracts according to the signs, normalizes, and repacks. The testbench drives operand pairs whose exact (and exactly representable) sums were computed offline and checks the output bit pattern.
// IEEE-754 single-precision floating-point adder (combinational).
// Scope: normal numbers and zero; round toward zero (truncation).
// Out of scope: denormals, NaN, Inf, round-to-nearest.
module fp32_add (
input [31:0] a,
input [31:0] b,
output reg [31:0] y
);
// unpack
wire sa = a[31], sb = b[31];
wire [7:0] ea = a[30:23], eb = b[30:23];
wire [22:0] fa = a[22:0], fb = b[22:0];
wire a_is_zero = (ea == 8'd0) && (fa == 23'd0);
wire b_is_zero = (eb == 8'd0) && (fb == 23'd0);
// significand with hidden/implicit 1 (24 bits, 1.f)
wire [23:0] siga = {1'b1, fa};
wire [23:0] sigb = {1'b1, fb};
// pick operand of larger magnitude (by exponent, then significand)
wire a_bigger = (ea > eb) || ((ea == eb) && (siga >= sigb));
wire s_big = a_bigger ? sa : sb;
wire [7:0] e_big = a_bigger ? ea : eb;
wire [23:0] sig_bg = a_bigger ? siga : sigb;
wire [7:0] e_sml = a_bigger ? eb : ea;
wire [23:0] sig_sm = a_bigger ? sigb : siga;
wire [7:0] diff = e_big - e_sml; // exponent difference
// align smaller operand (shift right, truncate dropped bits)
wire [23:0] sig_al = (diff >= 8'd24) ? 24'd0 : (sig_sm >> diff);
wire same_sign = (sa == sb);
reg [24:0] mag; // 25-bit to catch add carry
reg [7:0] e_res;
reg s_res;
integer k;
always @(*) begin
if (a_is_zero && b_is_zero) begin
y = 32'h00000000;
end else if (a_is_zero) begin
y = b;
end else if (b_is_zero) begin
y = a;
end else begin
s_res = s_big;
e_res = e_big;
if (same_sign) begin
mag = {1'b0, sig_bg} + {1'b0, sig_al};
if (mag[24]) begin // carried into bit 24
mag = mag >> 1; // truncate (round toward zero)
e_res = e_res + 8'd1;
end
end else begin
mag = {1'b0, sig_bg} - {1'b0, sig_al};
if (mag == 25'd0) begin
s_res = 1'b0; // exact cancellation -> +0
end else begin
// left-normalize until bit 23 is set
for (k = 0; k < 24; k = k + 1) begin
if (mag[23] == 1'b0) begin
mag = mag << 1;
e_res = e_res - 8'd1;
end
end
end
end
if (mag == 25'd0)
y = {s_res, 8'd0, 23'd0};
else
y = {s_res, e_res, mag[22:0]};
end
end
endmodule
module tb;
reg [31:0] a, b;
wire [31:0] y;
integer errors = 0;
fp32_add dut (.a(a), .b(b), .y(y));
task check;
input [31:0] ina, inb, exp;
input [127:0] name;
begin
a = ina; b = inb; #1;
if (y === exp)
$display("PASS %0s : %h + %h = %h", name, ina, inb, y);
else begin
$display("FAIL %0s : %h + %h = %h (expected %h)", name, ina, inb, y, exp);
errors = errors + 1;
end
end
endtask
initial begin
// Known-answer vectors (exact truncated sums computed offline).
check(32'h3F800000, 32'h40000000, 32'h40400000, "1.0+2.0=3.0");
check(32'h3FC00000, 32'hBF000000, 32'h3F800000, "1.5+(-0.5)=1.0");
check(32'h3F800000, 32'h00000000, 32'h3F800000, "1.0+0.0=1.0");
check(32'h41200000, 32'h40A00000, 32'h41700000, "10.0+5.0=15.0");
check(32'h42F60000, 32'h42F60000, 32'h43760000, "123+123=246");
if (errors == 0)
$display("ALL TESTS PASSED");
else
$display("%0d TEST(S) FAILED", errors);
$finish;
end
endmodule
PASS 1.0+2.0=3.0 : 3f800000 + 40000000 = 40400000 PASS 1.5+(-0.5)=1.0 : 3fc00000 + bf000000 = 3f800000 PASS 1.0+0.0=1.0 : 3f800000 + 00000000 = 3f800000 PASS 10.0+5.0=15.0 : 41200000 + 40a00000 = 41700000 PASS 123+123=246 : 42f60000 + 42f60000 = 43760000 ALL TESTS PASSEDThe log shows five known-answer cases passing:
1.0+2.0=3.0,1.5+(-0.5)=1.0,1.0+0.0=1.0,10.0+5.0=15.0, and123+123=246. Each expected value is an exactly representable FP32 number, so truncation equals the true sum and a wrong output bit anywhere would print FAIL. All five print PASS and the run ends with ALL TESTS PASSED.A sign-handling variant that makes the magnitude comparison and the add-versus-subtract decision explicit. It stresses effective subtraction: opposite-sign operands, exact cancellation to +0, and results that take the sign of the larger-magnitude operand. The sweep uses eight signed known-answer vectors.
// IEEE-754 single-precision FP adder, sign-handling / effective-subtraction focus. // Same scope: normal numbers + zero, round toward zero (truncation). // This variant makes the magnitude comparison and the add-vs-subtract // decision explicit, and sweeps many signed known-answer vectors, // including exact cancellation to +0 and sign-dominated results. module fp32_addsub ( input [31:0] a, input [31:0] b, output reg [31:0] y ); wire sa = a[31], sb = b[31]; wire [7:0] ea = a[30:23], eb = b[30:23]; wire [22:0] fa = a[22:0], fb = b[22:0]; wire a_is_zero = (ea == 8'd0) && (fa == 23'd0); wire b_is_zero = (eb == 8'd0) && (fb == 23'd0); wire [23:0] siga = {1'b1, fa}; wire [23:0] sigb = {1'b1, fb}; // explicit magnitude comparison: {exponent, significand} wire [31:0] maga = {ea, siga}; wire [31:0] magb = {eb, sigb}; wire a_ge = (maga >= magb); wire s_big = a_ge ? sa : sb; wire [7:0] e_big = a_ge ? ea : eb; wire [23:0] sig_bg = a_ge ? siga : sigb; wire [7:0] e_sml = a_ge ? eb : ea; wire [23:0] sig_sm = a_ge ? sigb : siga; wire [7:0] diff = e_big - e_sml; wire [23:0] sig_al = (diff >= 8'd24) ? 24'd0 : (sig_sm >> diff); wire eff_sub = (sa != sb); // opposite signs -> effective subtraction reg [24:0] mag; reg [7:0] e_res; reg s_res; integer k; always @(*) begin if (a_is_zero && b_is_zero) y = 32'h00000000; else if (a_is_zero) y = b; else if (b_is_zero) y = a; else begin s_res = s_big; e_res = e_big; if (!eff_sub) begin mag = {1'b0, sig_bg} + {1'b0, sig_al}; if (mag[24]) begin mag = mag >> 1; // truncate low bit e_res = e_res + 8'd1; end end else begin mag = {1'b0, sig_bg} - {1'b0, sig_al}; if (mag == 25'd0) s_res = 1'b0; // +0 on exact cancellation else for (k = 0; k < 24; k = k + 1) if (mag[23] == 1'b0) begin mag = mag << 1; e_res = e_res - 8'd1; end end y = (mag == 25'd0) ? {1'b0, 8'd0, 23'd0} : {s_res, e_res, mag[22:0]}; end end endmodule module tb; reg [31:0] a, b; wire [31:0] y; integer errors = 0; fp32_addsub dut (.a(a), .b(b), .y(y)); task check; input [31:0] ina, inb, exp; input [95:0] name; begin a = ina; b = inb; #1; if (y === exp) $display("PASS %0s : %h + %h = %h", name, ina, inb, y); else begin $display("FAIL %0s : %h + %h = %h (exp %h)", name, ina, inb, y, exp); errors = errors + 1; end end endtask initial begin // effective subtraction, result zero check(32'h3F800000, 32'hBF800000, 32'h00000000, "1-1=+0"); check(32'hC0800000, 32'h40800000, 32'h00000000, "-4+4=+0"); // sign of larger magnitude wins check(32'h40400000, 32'hBF800000, 32'h40000000, "3-1=2"); check(32'hBF800000, 32'h40400000, 32'h40000000, "-1+3=2"); check(32'h3F800000, 32'hC0400000, 32'hC0000000, "1-3=-2"); // same-sign add (both negative) check(32'hC0800000, 32'hC0800000, 32'hC1000000, "-4+-4=-8"); // exponent alignment under subtraction check(32'h3FC00000, 32'hBF000000, 32'h3F800000, "1.5-0.5=1"); // larger pair check(32'h42C00000, 32'h42000000, 32'h43000000, "96+32=128"); if (errors == 0) $display("ALL TESTS PASSED"); else $display("%0d TEST(S) FAILED", errors); $finish; end endmodulePASS 1-1=+0 : 3f800000 + bf800000 = 00000000
PASS -4+4=+0 : c0800000 + 40800000 = 00000000
PASS 3-1=2 : 40400000 + bf800000 = 40000000
PASS -1+3=2 : bf800000 + 40400000 = 40000000
PASS 1-3=-2 : 3f800000 + c0400000 = c0000000
PASS -4+-4=-8 : c0800000 + c0800000 = c1000000
PASS 1.5-0.5=1 : 3fc00000 + bf000000 = 3f800000
PASS 96+32=128 : 42c00000 + 42000000 = 43000000
ALL TESTS PASSEDThe log covers effective subtraction and signs:
1-1and-4+4both give+0(0x00000000),3-1=2,-1+3=2,1-3=-2, same-sign-4+-4=-8, the alignment case1.5-0.5=1, and96+32=128. Every expected pattern was computed independently; all eight print PASS, ending in ALL TESTS PASSED, which confirms the cancellation-to-zero and sign-selection logic.The IEEE-754 single-precision fields
A 32-bit float is three fields packed MSB-first: 1 sign bit, 8 exponent bits, and 23 fraction (mantissa) bits.
- Sign (bit 31): 0 for positive, 1 for negative.
- Exponent (bits 30:23): a biased integer. The stored value is the real exponent plus a bias of 127, so stored 127 means an exponent of 0.
- Fraction (bits 22:0): the bits after the binary point of a normalized
1.fsignificand.In the code this is simply
sign=x[31]; exp=x[30:23]; frac=x[22:0];. The value of a normal number is(-1)^sign × 1.frac × 2^(exp-127).The hidden (implicit) leading 1
For a normal number the significand is always of the form
1.frac: the leading 1 is never stored because it is always present. Hardware must put it back before doing arithmetic, which is why both designs build a 24-bit significand as{1'b1, frac}. That extra bit is the difference between the 23 bits you can see and the 24 bits of real precision. Zero is the special case where the exponent field is 0; here it is handled explicitly so the implicit 1 is not wrongly inserted.Exponent alignment
You can only add significands that share the same exponent, so the operand with the smaller exponent is shifted right by the exponent difference
d = e_big - e_sml. Each right shift divides the significand by two and increases its effective exponent by one, until both matche_big.In this truncating design the bits shifted out are simply dropped (
sig_sm >> d). A fully compliant unit would instead keep guard, round, and sticky bits from that shift to round correctly. Whendis 24 or more the smaller operand is negligible and aligns to zero.Add, subtract, and normalization
With the significands aligned, the signs decide the operation. Equal signs mean a true add; the 24-bit significands can overflow into a 25th bit, so on carry-out the result is shifted right by one and the exponent incremented. Opposite signs mean an effective subtraction of the smaller magnitude from the larger; the result always takes the sign of the larger-magnitude operand.
Subtraction can produce leading zeros (massive cancellation), so the result is left-normalized: shift left until bit 23 is 1 again, decrementing the exponent per shift. If the subtraction is exactly zero, the output is forced to
+0. Finally the hidden 1 is stripped and the low 23 bits become the packed fraction.Why this scopes to truncation and normals
Rounding is the subtle part of a real FPU. By rounding toward zero (truncation) and testing only sums that are exactly representable in FP32, the expected results equal the true mathematical sum, so a golden value can be stated as a fixed 32-bit constant and checked bit-for-bit — no floating-point reference model is needed inside the testbench. Restricting to normal numbers and zero keeps the datapath to the core unpack–align–add–normalize–repack loop, which is exactly what interviewers probe.
What full IEEE-754 compliance would add
Rounding
The default mode is round-to-nearest-even, which needs guard/round/sticky bits captured during alignment and normalization, plus a possible post-rounding re-normalization if rounding carries out.
Subnormals
When the exponent field is 0 but the fraction is non-zero, the number is denormal: there is no hidden 1 and the exponent is fixed at the minimum. Supporting these adds gradual underflow handling.
Special values
Exponent 255 encodes Infinity (fraction 0) and NaN (fraction non-zero). A compliant adder must propagate NaN, handle Inf−Inf as NaN, and set exception flags (overflow, underflow, inexact, invalid).
Synthesis notes
Both modules are purely combinational. The normalization loop is written as a bounded
forwith a compile-time constant bound, so it unrolls into a priority/leading-zero-count plus a barrel shifter rather than real iteration — synthesizable, but it sits on the critical path. In a real pipeline you would register the unpack, align, add, and normalize stages separately, and replace the linear shift search with an explicit leading-zero counter feeding a barrel shifter. The variable right shift for alignment also infers a barrel shifter.FAQ
Why is 1.0+2.0 given as hex 0x3F800000 + 0x40000000?
Those are the IEEE-754 bit patterns for 1.0 and 2.0; the adder works on raw 32-bit words, so the testbench drives and checks hex directly.
Why does cancellation give +0 and not -0?
This design forces exact cancellation to +0 for a deterministic result. IEEE-754 actually specifies -0 only under round-toward-negative; round-toward-zero yields +0, which is what the code does.
Can I widen it to double precision?
Yes — use 11 exponent bits, a bias of 1023, and a 52-bit fraction (53-bit significand). The datapath structure is identical; only the field widths and bias change.
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