Serial Adder in Verilog: Bit-Serial FSM with a Carry Flip-Flop

A parallel 8-bit adder spends 8 full-adders in one clock; a serial adder verilog code design spends a single full-adder across 8 clocks, processing one bit per cycle starting from the LSB. The running carry is remembered in a one-bit flip-flop that feeds the next bit’s addition, so the hardware shrinks dramatically at the cost of latency.

This page builds the classic bit-serial adder as a small FSM with two shift registers and a carry flip-flop, then shows the underlying full-adder bit-cell on its own. Both come with self-checking Icarus Verilog testbenches that add 100 + 55, 200 + 99 (note the carry-out when the result exceeds 255), and other cases.

Bit-serial adder: FSM + shift registers + carry FF

On load the operands are captured into LSB-first shift registers. For the next 8 clocks the design adds a_sr[0], b_sr[0] and the stored carry, shifts the sum bit into the MSB of sum, shifts the operands right, and updates the carry flip-flop. After 8 cycles done asserts and cout holds the 9th (carry-out) bit.

// Bit-serial adder: FSM + carry flip-flop, LSB-first
module serial_adder(
  input            clk, rst_n,
  input            load,
  input      [7:0] a_in, b_in,
  output reg [7:0] sum,
  output reg       done,
  output reg       cout
);
  reg [7:0] a_sr, b_sr;   // shift registers (LSB-first)
  reg       carry;        // carry flip-flop
  reg [3:0] cnt;          // 0..8 counter (FSM state)
  reg       busy;
  wire      bitsum = a_sr[0] ^ b_sr[0] ^ carry;
  wire      cnext  = (a_sr[0] & b_sr[0]) | (a_sr[0] & carry) | (b_sr[0] & carry);

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      a_sr <= 8'd0; b_sr <= 8'd0; carry <= 1'b0;
      cnt  <= 4'd0; busy <= 1'b0; sum <= 8'd0; done <= 1'b0; cout <= 1'b0;
    end else if (load) begin
      a_sr <= a_in; b_sr <= b_in; carry <= 1'b0;
      cnt  <= 4'd0; busy <= 1'b1; done <= 1'b0; cout <= 1'b0;
    end else if (busy) begin
      // compute one bit, shift it into MSB of sum, shift operands right
      sum   <= {bitsum, sum[7:1]};
      carry <= cnext;
      a_sr  <= {1'b0, a_sr[7:1]};
      b_sr  <= {1'b0, b_sr[7:1]};
      cnt   <= cnt + 4'd1;
      if (cnt == 4'd7) begin
        busy <= 1'b0;
        done <= 1'b1;
        cout <= cnext;   // carry-out after the 8th bit
      end
    end else begin
      done <= 1'b0;      // done is a 1-cycle pulse-ish; keep sum stable
    end
  end
endmodule

module tb;
  reg clk, rst_n, load;
  reg [7:0] a_in, b_in;
  wire [7:0] sum;
  wire done, cout;
  integer errors = 0;

  serial_adder dut(.clk(clk), .rst_n(rst_n), .load(load),
                   .a_in(a_in), .b_in(b_in), .sum(sum), .done(done), .cout(cout));

  always #5 clk = ~clk;

  task run_case;
    input [7:0] A, B;
    input [8:0] EXP;   // 9-bit expected (incl carry-out)
    begin
      @(negedge clk); a_in = A; b_in = B; load = 1'b1;
      @(negedge clk); load = 1'b0;
      // 8 shift clocks
      repeat (8) @(negedge clk);
      if ({cout, sum} !== EXP) begin
        errors = errors + 1;
        $display("FAIL: %0d + %0d => {cout,sum}=%0d (expected %0d)", A, B, {cout,sum}, EXP);
      end else begin
        $display("PASS: %0d + %0d = %0d (cout=%b sum=%0d)", A, B, EXP, cout, sum);
      end
    end
  endtask

  initial begin
    clk = 0; rst_n = 0; load = 0; a_in = 0; b_in = 0;
    @(negedge clk); rst_n = 1;
    run_case(8'd100, 8'd55,  9'd155);   // 155 fits in 8 bits, cout=0
    run_case(8'd200, 8'd99,  9'd299);   // 299 > 255 -> cout=1, sum=43
    run_case(8'd255, 8'd1,   9'd256);   // overflow, cout=1 sum=0
    run_case(8'd0,   8'd0,   9'd0);
    if (errors == 0) $display("PASS: all serial-adder cases correct");
    else             $display("FAIL: %0d case(s) wrong", errors);
    $finish;
  end
endmodule
PASS: 100 + 55 = 155 (cout=0 sum=155)
PASS: 200 + 99 = 299 (cout=1 sum=43)
PASS: 255 + 1 = 256 (cout=1 sum=0)
PASS: 0 + 0 = 0 (cout=0 sum=0)
PASS: all serial-adder cases correct

200 + 99 = 299 overflows 8 bits, so cout=1 and the low byte reads 43; 255 + 1 wraps to sum=0 with cout=1.

The heart of it: a full-adder bit-cell with a carry register

Strip away the shift registers and the serial adder is just one full adder whose carry-out is latched and fed back as the next carry-in. Here operands stream in LSB-first one bit per clock; clr zeroes the carry before each new word.

// Serial adder as a single full-adder bit-cell + carry flip-flop.
// Operands arrive LSB-first, one bit per clock, on a and b. The cell adds
// them with the stored carry and emits s; carry is held for the next bit.
module serial_add_cell(
  input       clk, rst_n, clr,
  input       a, b,
  output      s,
  output reg  carry
);
  assign s = a ^ b ^ carry;                         // sum bit
  wire   cnx = (a & b) | (a & carry) | (b & carry); // carry-out
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n)   carry <= 1'b0;
    else if (clr) carry <= 1'b0;   // zero carry BEFORE a new word
    else          carry <= cnx;
  end
endmodule

module tb;
  reg clk, rst_n, clr, a, b;
  wire s, carry;
  serial_add_cell dut(.clk(clk), .rst_n(rst_n), .clr(clr), .a(a), .b(b), .s(s), .carry(carry));
  always #5 clk = ~clk;

  reg  [8:0] result;   // 8 sum bits + final carry-out
  integer i;
  integer errors = 0;

  task add_bytes;
    input [7:0] A, B;
    input [8:0] EXP;
    begin
      @(negedge clk); clr = 1'b1;      // one cycle to clear carry
      @(negedge clk); clr = 1'b0;      // carry is now 0
      for (i = 0; i < 8; i = i + 1) begin
        a = A[i]; b = B[i];            // present bit i (LSB first)
        #1 result[i] = s;             // sum bit = a^b^carry (combinational)
        @(negedge clk);               // posedge latches carry for next bit
      end
      result[8] = carry;              // carry-out after the last bit
      if (result !== EXP) begin
        errors = errors + 1;
        $display("FAIL: %0d + %0d => %0d (expected %0d)", A, B, result, EXP);
      end else
        $display("PASS: %0d + %0d = %0d", A, B, EXP);
    end
  endtask

  initial begin
    clk = 0; rst_n = 0; clr = 0; a = 0; b = 0; result = 0;
    @(negedge clk); rst_n = 1;
    add_bytes(8'd100, 8'd55,  9'd155);
    add_bytes(8'd200, 8'd99,  9'd299);
    add_bytes(8'd127, 8'd128, 9'd255);
    add_bytes(8'd255, 8'd255, 9'd510);
    if (errors == 0) $display("PASS: serial add-cell streams correct");
    else             $display("FAIL: %0d wrong", errors);
    $finish;
  end
endmodule
PASS: 100 + 55 = 155
PASS: 200 + 99 = 299
PASS: 127 + 128 = 255
PASS: 255 + 255 = 510
PASS: serial add-cell streams correct

How the bit-serial addition works

Binary addition is inherently serial from the LSB up: each column needs the carry produced by the column below it. A ripple-carry adder unrolls all 8 columns in space (8 full-adders); the serial adder folds them into time, reusing one full-adder for 8 consecutive clocks. The only state that must survive between columns is a single carry bit, held in the carry flip-flop. The operands live in shift registers so that bit i appears at position 0 on clock i, and each computed sum bit is shifted into the result register.

The full-adder equations

Every cycle the cell evaluates the two canonical full-adder expressions: the sum bit s = a ^ b ^ carry and the carry-out cnext = (a&b) | (a&carry) | (b&carry). The sum is purely combinational, while carry is registered so the value used on clock i is the carry-out generated on clock i−1. For an N-bit addition the carry-out after N clocks is the (N+1)th bit of the true sum — exactly what cout captures in the first example.

Synthesis notes

The area win is real: one full-adder, two N-bit shift registers, a carry flip-flop and a small modulo-(N+1) counter, versus N full-adders for a ripple adder. The trade-off is throughput — N clocks per addition instead of one — and a lower achievable clock period only helps if the surrounding datapath is also bit-serial. Keep the reset asynchronous and the carry update synchronous, and drive load/clr from the controlling FSM so the carry is always cleared before the LSB is added.

Common pitfalls

The classic bug is clearing the carry at the wrong time. The carry must be zeroed before the LSB is processed, never on the same edge that should latch the LSB's carry-out — do the latter and every multi-byte result loses its lowest carry. A second trap is forgetting the carry-out: an 8-bit sum alone cannot represent 200 + 99, so expose a 9th bit (cout) or the testbench will wrongly flag a wrap as a failure. Finally, remember the latency: downstream logic must wait for done, not sample sum mid-stream.

Interview angle

Interviewers love this design because it forces you to separate combinational logic (the full adder) from sequential state (the carry FF and shift registers), and to reason about latency vs. area. Be ready to state the cycle count (N clocks after load for an N-bit add), explain why the carry flip-flop is the only inter-column state, and sketch how the same idea extends to a serial subtractor (feed ~b and initialise the carry to 1 for two's-complement) or a serial multiplier (add-and-shift).

Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.
▶ Try it in the ChipXpert Lab

Open this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.

Run it in the Lab

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
Next batch starts 18th November 2026
Start your VLSI career with ChipXpert

Live online and classroom batches in Hyderabad & Bengaluru. Fill in your details and a counsellor will call you back.

  • Real EDA tools in your browser: industry-standard EDAReal EDA tools in your browser
  • Recorded sessions on the elearn portalRecorded sessions
  • Placement assistance: resume, mock interviewsPlacement assistance
  • Merit scholarship up to 60% · EMI optionsScholarship up to 60%
Hi! Ask me about courses, fees, batches or discounts. ×
BENGALURU
HYDERABAD