Binary to BCD in Verilog: Double-Dabble Shift-and-Add-3

Converting a binary number into packed binary to BCD verilog code is a classic exam and interview problem, and the cleanest hardware solution is the double-dabble (also called shift-and-add-3) algorithm. It needs no dividers or lookup tables — just shifts and a conditional +3 correction per decimal digit.

Below we build an 8-bit converter two ways: a purely combinational bin2bcd that produces 3 BCD digits (hundreds, tens, ones) for inputs 0–255, and a clocked FSM that performs one shift per clock with a start/done handshake. Both come with deterministic, self-checking testbenches.

Combinational double-dabble converter

The algorithm scans the binary input from MSB to LSB. Before each left shift, any BCD nibble already holding a value ≥5 gets +3 added, so that after the shift it stays a valid decimal digit (0–9). After W shifts the 12-bit register holds three packed BCD digits.

// Combinational binary-to-BCD using double-dabble (shift-and-add-3)
module bin2bcd #(parameter W=8)(input [W-1:0] bin, output reg [11:0] bcd);
  integer i, j;
  reg [11:0] d; // 3 BCD nibbles: hundreds, tens, ones
  always @(*) begin
    d = 12'd0;
    for (i = W-1; i >= 0; i = i - 1) begin
      // add 3 to any nibble >= 5 BEFORE the shift
      for (j = 0; j < 12; j = j + 4)
        if (d[j +: 4] >= 5)
          d[j +: 4] = d[j +: 4] + 4'd3;
      // shift left, bring in next binary bit
      d = {d[10:0], bin[i]};
    end
    bcd = d;
  end
endmodule

module tb;
  reg  [7:0] bin;
  wire [11:0] bcd;
  wire [3:0] h = bcd[11:8];
  wire [3:0] t = bcd[7:4];
  wire [3:0] o = bcd[3:0];
  integer errors;

  bin2bcd #(.W(8)) dut(.bin(bin), .bcd(bcd));

  task check;
    input [7:0] v;
    input [3:0] eh, et, eo;
    begin
      bin = v; #1;
      $display("bin=%3d -> %0d%0d%0d (h=%0d t=%0d o=%0d)", v, h, t, o, h, t, o);
      if (h !== eh || t !== et || o !== eo) begin
        errors = errors + 1;
        $display("  MISMATCH expected %0d%0d%0d", eh, et, eo);
      end
    end
  endtask

  initial begin
    errors = 0;
    check(8'd0,   0,0,0);
    check(8'd9,   0,0,9);
    check(8'd10,  0,1,0);
    check(8'd99,  0,9,9);
    check(8'd123, 1,2,3);
    check(8'd255, 2,5,5);
    if (errors == 0) $display("ALL PASS");
    else $display("FAIL: %0d errors", errors);
    $finish;
  end
endmodule
bin=  0 -> 000 (h=0 t=0 o=0)
bin=  9 -> 009 (h=0 t=0 o=9)
bin= 10 -> 010 (h=0 t=1 o=0)
bin= 99 -> 099 (h=0 t=9 o=9)
bin=123 -> 123 (h=1 t=2 o=3)
bin=255 -> 255 (h=2 t=5 o=5)
ALL PASS

The sweep covers the corner values 0, 9, 10, 99, 123 and 255; every digit is asserted against its expected value, and the run ends with ALL PASS.

Clocked (iterative) version with start/done

For area-critical designs you process one bit per clock instead of unrolling all W iterations into combinational logic. A small counter tracks progress; start loads the input and done pulses when the 12-bit result is ready.

// Iterative (clocked) double-dabble: one shift per clock, start/done handshake
module bin2bcd_seq(
  input clk, input rst, input start,
  input [7:0] bin,
  output reg [11:0] bcd,
  output reg done
);
  reg busy;
  reg [3:0] count;      // bits processed (0..8)
  reg [7:0] sreg;       // remaining binary bits
  reg [11:0] d;
  integer j;

  always @(posedge clk) begin
    if (rst) begin
      busy <= 1'b0; done <= 1'b0; count <= 4'd0;
      d <= 12'd0; sreg <= 8'd0; bcd <= 12'd0;
    end else if (start && !busy) begin
      busy <= 1'b1; done <= 1'b0; count <= 4'd0;
      d <= 12'd0; sreg <= bin;
    end else if (busy) begin
      if (count == 4'd8) begin
        bcd <= d; done <= 1'b1; busy <= 1'b0;
      end else begin
        // add-3 to nibbles >= 5, then shift in MSB of sreg
        for (j = 0; j < 12; j = j + 4)
          if (d[j +: 4] >= 5) d[j +: 4] = d[j +: 4] + 4'd3;
        d <= {d[10:0], sreg[7]};
        sreg <= {sreg[6:0], 1'b0};
        count <= count + 4'd1;
      end
    end
  end
endmodule

module tb;
  reg clk, rst, start;
  reg [7:0] bin;
  wire [11:0] bcd;
  wire done;
  integer errors;

  bin2bcd_seq dut(.clk(clk), .rst(rst), .start(start), .bin(bin), .bcd(bcd), .done(done));

  initial clk = 0;
  always #5 clk = ~clk;

  initial begin
    errors = 0;
    rst = 1; start = 0; bin = 8'd0;
    @(posedge clk); @(posedge clk);
    rst = 0; @(posedge clk);
    bin = 8'd255; start = 1; @(posedge clk);
    start = 0;
    wait (done);
    @(posedge clk);
    $display("bin=255 -> h=%0d t=%0d o=%0d", bcd[11:8], bcd[7:4], bcd[3:0]);
    if (bcd[11:8] !== 4'd2 || bcd[7:4] !== 4'd5 || bcd[3:0] !== 4'd5) begin
      errors = errors + 1; $display("  MISMATCH expected 2,5,5");
    end
    if (errors == 0) $display("ALL PASS");
    else $display("FAIL: %0d errors", errors);
    $finish;
  end
endmodule
bin=255 -> h=2 t=5 o=5
ALL PASS

The testbench resets, pulses start with input 255, waits for done, then checks the result decodes to 2, 5, 5.

How double-dabble works

The trick exploits a property of binary-coded decimal: a left shift multiplies a value by two, but a decimal digit can only hold 0–9. If a nibble is 5 or more, doubling it would overflow past 9 and corrupt the digit. Adding 3 before the shift pre-biases the nibble so that after the shift (a ×2) it lands on the correct value with a proper carry into the next decimal column. The scan runs MSB-first for W iterations, so an 8-bit input needs eight shift-and-correct steps and yields three BCD digits (hundreds, tens, ones) in a 12-bit register.

Synthesis and timing notes

The combinational version synthesizes to a chain of eight correction-and-shift stages. Each stage is a few comparators (nibble ≥5) and adders, so the critical path grows roughly linearly with input width — fine for 8-bit, but for wide inputs the long combinational delay may limit fmax. The clocked version trades latency (W+2 cycles here) for a tiny, pipeline-friendly datapath that reuses one correction stage per clock, which is the better choice when area or timing closure matters. Note the blocking = inside the synchronous for loop: it computes the corrected value within the same clock evaluation before the non-blocking <= registers the shifted result.

Common pitfalls

The most frequent bug is applying the +3 correction after the shift instead of before — that produces wrong digits for any value above 4. Another is sizing the BCD register too small: an 8-bit input reaches 255, which needs three digits (12 bits), not two. Also watch the loop direction; scanning LSB-first instead of MSB-first breaks the algorithm entirely. Finally, keep this Verilog-2005 clean: use reg/wire and integer loop variables, not SystemVerilog logic or always_comb, if your toolchain is locked to the 2005 standard.

Interview angle

Interviewers love this problem because it separates candidates who memorize code from those who understand why the +3 works. Be ready to explain the pre-bias intuition, to state that W bits take W shift iterations, and to size the output as ceil(W / 3.32) decimal digits. A strong follow-up answer contrasts the combinational unrolled design (low latency, long critical path) against the iterative FSM (small area, multi-cycle), and mentions that the same shift-and-add-3 structure maps directly to an FPGA shift register.

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