Convert Binary numbers to BCD in VHDL and Verilog
Drive a 7-Segment Display using a Double Dabbler on an FPGA
This module takes an input binary vector and converts it to Binary Coded Decimal (BCD). Binary coded decimal is used to represent a decimal number with four bits. This can be used to convert a binary number to a decimal number than can be displayed on a 7-Segment LED display. The algorithm used in the code below is known as a Double Dabble.
Binary coded decimal uses four bits per digit to represent a decimal number. For example the number 159 in decimal takes 12 bits to represent. This is useful for applications that interface to 7-Segment LEDs, among other things. The reason for this is that each 7-Segment display is treated individually (each gets 4 bits of the 12 bit number in the example above). The FPGA designer needs to know how to drive each digit, and uses BCD to do this. The table for BCD is below.
| BCD and Decimal Numbers | |
|---|---|
| BCD | Decimal |
| 0000 | 0 |
| 0001 | 1 |
| 0010 | 2 |
| 0011 | 3 |
| 0100 | 4 |
| 0101 | 5 |
| 0110 | 6 |
| 0111 | 7 |
| 1000 | 8 |
| 1001 | 9 |
| others | undefined |
Let’s look at 159. The hundreds digit 1 is represented in binary by 0001. The tens digit 5 is represented in binary by 0101. The ones digit 9 is represented in binary by 1001. The entire number 159 in BCD is therefore: 000101011001. However 159 in binary is represented by 10011111. Again we need a way to convert this binary number 10011111 to its BCD equivalent 000101011001. To do this, we will use the Double Dabble algorithm.
The Double Dabble Algorithm is described in detail on the linked Wikipedia page. But basically it takes the input binary number as a start. It shifts it one bit at a time into the BCD output vector. It then looks at each 4-bit BCD digit independently. If any of the digits are greater than 4, that digit is incremented by 3. This loop continues for each bit in the input binary vector. See the image below for a visual depiction of how the Finite State Machine is written.
VHDL Implementation (Verilog Implementation below)
The VHDL implementation makes use of variables and the input and output widths can be changed by setting the generics. Both variables and generics help to make the code more clean and more flexible.
Binary_to_BCD.vhd:
-------------------------------------------------------------------------------
-- File Downloaded from http://www.nandland.com
-------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity Binary_to_BCD is
generic (
g_INPUT_WIDTH : in positive;
g_DECIMAL_DIGITS : in positive
);
port (
i_Clock : in std_logic;
i_Start : in std_logic;
i_Binary : in std_logic_vector(g_INPUT_WIDTH-1 downto 0);
o_BCD : out std_logic_vector(g_DECIMAL_DIGITS*4-1 downto 0);
o_DV : out std_logic
);
end entity Binary_to_BCD;
architecture rtl of Binary_to_BCD is
type t_BCD_State is (s_IDLE, s_SHIFT, s_CHECK_SHIFT_INDEX, s_ADD,
s_CHECK_DIGIT_INDEX, s_BCD_DONE);
signal r_SM_Main : t_BCD_State := s_IDLE;
-- The vector that contains the output BCD
signal r_BCD : std_logic_vector(g_DECIMAL_DIGITS*4-1 downto 0) := (others => '0');
-- The vector that contains the input binary value being shifted.
signal r_Binary : std_logic_vector(g_INPUT_WIDTH-1 downto 0) := (others => '0');
-- Keeps track of which Decimal Digit we are indexing
signal r_Digit_Index : natural range 0 to g_DECIMAL_DIGITS-1 := 0;
-- Keeps track of which loop iteration we are on.
-- Number of loops performed = g_INPUT_WIDTH
signal r_Loop_Count : natural range 0 to g_INPUT_WIDTH-1 := 0;
begin
Double_Dabble : process (i_Clock)
variable v_Upper : natural;
variable v_Lower : natural;
variable v_BCD_Digit : unsigned(3 downto 0);
begin
if rising_edge(i_Clock) then
case r_SM_Main is
-- Stay in this state until i_Start comes along
when s_IDLE =>
if i_Start = '1' then
r_BCD <= (others => '0');
r_Binary <= i_Binary;
r_SM_Main <= s_SHIFT;
else
r_SM_Main <= s_IDLE;
end if;
-- Always shift the BCD Vector until we have shifted all bits through
-- Shift the most significant bit of r_Binary into r_BCD lowest bit.
when s_SHIFT =>
r_BCD <= r_BCD(r_BCD'left-1 downto 0) & r_Binary(r_Binary'left);
r_Binary <= r_Binary(r_Binary'left-1 downto 0) & '0';
r_SM_Main <= s_CHECK_SHIFT_INDEX;
-- Check if we are done with shifting in r_Binary vector
when s_CHECK_SHIFT_INDEX =>
if r_Loop_Count = g_INPUT_WIDTH-1 then
r_Loop_Count <= 0;
r_SM_Main <= s_BCD_DONE;
else
r_Loop_Count <= r_Loop_Count + 1;
r_SM_Main <= s_ADD;
end if;
-- Break down each BCD Digit individually. Check them one-by-one to
-- see if they are greater than 4. If they are, increment by 3.
-- Put the result back into r_BCD Vector. Note that v_BCD_Digit is
-- unsigned. Numeric_std does not perform math on std_logic_vector.
when s_ADD =>
v_Upper := r_Digit_Index*4 + 3;
v_Lower := r_Digit_Index*4;
v_BCD_Digit := unsigned(r_BCD(v_Upper downto v_Lower));
if v_BCD_Digit > 4 then
v_BCD_Digit := v_BCD_Digit + 3;
end if;
r_BCD(v_Upper downto v_Lower) <= std_logic_vector(v_BCD_Digit);
r_SM_Main <= s_CHECK_DIGIT_INDEX;
-- Check if we are done incrementing all of the BCD Digits
when s_CHECK_DIGIT_INDEX =>
if r_Digit_Index = g_DECIMAL_DIGITS-1 then
r_Digit_Index <= 0;
r_SM_Main <= s_SHIFT;
else
r_Digit_Index <= r_Digit_Index + 1;
r_SM_Main <= s_ADD;
end if;
when s_BCD_DONE =>
r_SM_Main <= s_IDLE;
when others =>
r_SM_Main <= s_IDLE;
end case;
end if; -- rising_edge(i_Clock)
end process Double_Dabble;
o_DV <= '1' when r_SM_Main = s_BCD_DONE else '0';
o_BCD <= r_BCD;
end architecture rtl;
Verilog Implementation
Binary_To_BCD.v:
///////////////////////////////////////////////////////////////////////////////
// File Downloaded from http://www.nandland.com
///////////////////////////////////////////////////////////////////////////////
module Binary_to_BCD
#(parameter INPUT_WIDTH,
parameter DECIMAL_DIGITS)
(
input i_Clock,
input [INPUT_WIDTH-1:0] i_Binary,
input i_Start,
//
output [DECIMAL_DIGITS*4-1:0] o_BCD,
output o_DV
);
parameter s_IDLE = 3'b000;
parameter s_SHIFT = 3'b001;
parameter s_CHECK_SHIFT_INDEX = 3'b010;
parameter s_ADD = 3'b011;
parameter s_CHECK_DIGIT_INDEX = 3'b100;
parameter s_BCD_DONE = 3'b101;
reg [2:0] r_SM_Main = s_IDLE;
// The vector that contains the output BCD
reg [DECIMAL_DIGITS*4-1:0] r_BCD = 0;
// The vector that contains the input binary value being shifted.
reg [INPUT_WIDTH-1:0] r_Binary = 0;
// Keeps track of which Decimal Digit we are indexing
reg [DECIMAL_DIGITS-1:0] r_Digit_Index = 0;
// Keeps track of which loop iteration we are on.
// Number of loops performed = INPUT_WIDTH
reg [7:0] r_Loop_Count = 0;
wire [3:0] w_BCD_Digit;
reg r_DV = 1'b0;
always @(posedge i_Clock)
begin
case (r_SM_Main)
// Stay in this state until i_Start comes along
s_IDLE :
begin
r_DV <= 1'b0;
if (i_Start == 1'b1)
begin
r_Binary <= i_Binary;
r_SM_Main <= s_SHIFT;
r_BCD <= 0;
end
else
r_SM_Main <= s_IDLE;
end
// Always shift the BCD Vector until we have shifted all bits through
// Shift the most significant bit of r_Binary into r_BCD lowest bit.
s_SHIFT :
begin
r_BCD <= r_BCD << 1;
r_BCD[0] <= r_Binary[INPUT_WIDTH-1];
r_Binary <= r_Binary << 1;
r_SM_Main <= s_CHECK_SHIFT_INDEX;
end
// Check if we are done with shifting in r_Binary vector
s_CHECK_SHIFT_INDEX :
begin
if (r_Loop_Count == INPUT_WIDTH-1)
begin
r_Loop_Count <= 0;
r_SM_Main <= s_BCD_DONE;
end
else
begin
r_Loop_Count <= r_Loop_Count + 1;
r_SM_Main <= s_ADD;
end
end
// Break down each BCD Digit individually. Check them one-by-one to
// see if they are greater than 4. If they are, increment by 3.
// Put the result back into r_BCD Vector.
s_ADD :
begin
if (w_BCD_Digit > 4)
begin
r_BCD[(r_Digit_Index*4)+:4] <= w_BCD_Digit + 3;
end
r_SM_Main <= s_CHECK_DIGIT_INDEX;
end
// Check if we are done incrementing all of the BCD Digits
s_CHECK_DIGIT_INDEX :
begin
if (r_Digit_Index == DECIMAL_DIGITS-1)
begin
r_Digit_Index <= 0;
r_SM_Main <= s_SHIFT;
end
else
begin
r_Digit_Index <= r_Digit_Index + 1;
r_SM_Main <= s_ADD;
end
end
s_BCD_DONE :
begin
r_DV <= 1'b1;
r_SM_Main <= s_IDLE;
end
default :
r_SM_Main <= s_IDLE;
endcase
end // always @ (posedge i_Clock)
assign w_BCD_Digit = r_BCD[r_Digit_Index*4 +: 4];
assign o_BCD = r_BCD;
assign o_DV = r_DV;
endmodule // Binary_to_BCD




It is giving error on line ” v_BCD_Digit := unsigned(r_BCD(v_Upper downto v_Lower)); ” in VHDL code
[Synth 8-690] width mismatch in assignment; target has 4 bits, source has 16 bits.
I am using Vivado 2018.1.
Is there any solution to it
You have a missing s_ADD label in
Binary_To_BCD.v text.
SyntaxHighlighter messed it up.
After fixing it, the code turned out to work.
I got same issue with Athern27. Is there any solution for this.
You need to change the VHDL version used by the CAD. For instance, the Quartus Prime Lite accepts only the standard IEEE 1076-1993 (Compiler Settings). To use the IEEE 1076-2008, you need a paid subscription/license.
“v_BCD_Digit := unsigned(r_BCD(v_Upper downto v_Lower));”
VHDL doesn’t allow this type of assignment. I made it work by using a case statement. e.g.:
when s_ADD =>
case r_Digit_Index is
when “000” =>
v_BCD_Digit
v_BCD_Digit 4) then v_BCD_Digit <= v_BCD_Digit + 3.
the third ADD state was another case statement assigning r_BCD partial vectors to v_BCD_Digit, based on r_Digit_Index.
The error can be fixed using a temporary variable to hold the indexed bits.
v_BCD_temp := r_BCD(v_Upper downto v_Lower);
v_BCD_Digit := unsigned(v_BCD_temp);
This is using Vivado 2024.2
This error can be fixed using a temporary variable to hold the indexed bits.
v_BCD_temp := r_BCD(v_Upper downto v_Lower);
v_BCD_Digit := unsigned(v_BCD_temp);
Tested in Vivado 2024.2.