VHDL coding tips and tricks: VHDL: 8 bit Binary to BCD converter with Testbench

Monday, April 19, 2010

VHDL: 8 bit Binary to BCD converter with Testbench

    All numerical values are fundamentally handled as binary numbers inside the FPGA. But that is not so human readable, isnt it? Even when we write a VHDL program, most of us would prefer to write, 
a <= 10; instead of a <= "1010";.

    When viewing signals in a simulation waveform, we can easily change the radix of the signal as per our convenience. But when we test the design on a real FPGA board, we would need to use dedicated display panels such as 7 segment decoders to see the binary numbers in decimal format. This is where BCD format comes in. 

    The decimal number 10, when converter to BCD format would be "10". Looks the same, except that, here each digit is given 4 bits for their storage. Though 4 bits can store from 0 to 15, we limit the range from 0 to 9, just like that of a regular decimal number.

    In this blog post, I want to share a VHDL function for converting an 8 bit binary number into a 3 digit (or 12 bit binary) BCD number. BCD stands for Binary Coded Decimal. The algorithm used is known as double dabble. You can read more on it here at, Double Dabble(wiki).

    A self checking testbench has been written as well, to verify the function. 

BCD Converter + Testbench:


library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

--empty entity for testbenches.
entity tb_bcd_conversion is
end tb_bcd_conversion;

architecture Behavioral of tb_bcd_conversion is

--Function definition
function to_bcd ( bin : unsigned(7 downto 0) ) return unsigned is
    variable i : integer:=0;
    variable bcd : unsigned(11 downto 0) := (others => '0');
begin
    for i in 7 downto 1 loop  --iterating 7 times.
        --left shifting the bits and padding to the lsb
        bcd := bcd(10 downto 0) & bin(i);  
        --increment 3 if BCD digit at 1's is greater than 4.
        if(bcd(3 downto 0) > 4) then 
            bcd(3 downto 0) := bcd(3 downto 0) + 3;
        end if;
        --increment 3 if BCD digit at 10's is greater than 4.
        if(bcd(7 downto 4) > 4) then 
            bcd(7 downto 4) := bcd(7 downto 4) + 3;
        end if;
        --we dont need to repeat the above if statement for 100's position. Why?
        --Because input is 8 bit, which means maximum value at 100's position is 2.
    end loop;
    bcd := bcd(10 downto 0) & bin(0);  --final left shifting
    return bcd;  --return the result
end function to_bcd;
--End of function definition

--signals used to test the function.
--They help us to view the results in simulation waveform
signal bcd_out : unsigned(11 downto 0);
signal bcd_out_int: integer;

begin

--process where we test the binary to bcd function
stimulus_process: process
--variables used for testing. 
--Varibales are useful because they get updated rightaway.
--But they cant be seen in simulation waveform, thats why we assign
--them to signals before exiting the process.
variable bcd_out_int_var : integer;
variable bcd_out_var : unsigned(11 downto 0);
begin
    --test for all the 256 values the 8 bit input can take.
    for i in 0 to 255 loop
        bcd_out_var := to_bcd(to_unsigned(i,8));
        --convert bcd to decimal value by multiplying respective digits with 1,10 and 100.
        bcd_out_int_var := to_integer(bcd_out_var(3 downto 0)) + 
            to_integer(bcd_out_var(7 downto 4))*10 +  
            to_integer(bcd_out_var(11 downto 8))*100;
        --the assert statement is used to implement a self checking testbench.
        --we dont need to manually verify if each input is correctly converted,
        --but the testbench does it for us. If the statement in the 'assert' is
        --incorrect a 'warning' message will be reported in modelsim
        assert bcd_out_int_var = i;
        --assign to signals to see the results in simulation waveform
        bcd_out_int <= bcd_out_int_var;
        bcd_out <= bcd_out_var;
        --let the results stay the same for some time, so that human eyes could catch it.
        wait for 10 ns;
    end loop; 
    wait;  --testing done. wait Endlessly.
end process;

end Behavioral;


Simulation Waveform:


Some sample inputs and the corresponding outputs are shown below:
binary = "01100011",      output = "0000 1001 1001"  (99).
binary = "11111110",      output = "0010 0101 0100"  (254).
binary = "10111011",      output = "0001 1000 0111"  (187).

A part of the simulation waveform from Modelsim is shared below:


simulation waveform of binary to bcd converter in vhdl modelsim


Schematic after synthesis:


The code was synthesised using Xilinx Vivado 2023.2. The schematic generated after synthesis is shared below:

schematic from xillinx vivado for bcd converter



Note :- The code can be modified to convert any length binary number to corresponding BCD digits. This require very little change in the code. May be you could try that as a homework. 

30 comments:

  1. Hi,

    is it possible to create these netlist-graphics from the command-line using xilinx tools?

    ReplyDelete
  2. @marvin2k : See this link from xilinx.
    http://www.xilinx.com/itp/xilinx4/data/docs/xst/command_line9.html

    ReplyDelete
  3. @vipin: this is to synthesize a design? I meant something like the "RTL Viewer" mentioned in other posts (sorry, in fact I wanted to post in another blogentry of your blog...) to view the generated netlist.

    ReplyDelete
  4. There is a command like this in that link :
    run -ifn watchvhd.vhd -ifmt VHDL -ofn watchvhd.ngc -ofmt NGC -p xcv50-bg256-6 -opt_mode Speed
    -opt_level 1

    This will generate the ngc file.You have to open this file with a xilinx ngc file viewer.

    ReplyDelete
  5. Yes, and my question was if one can invoke such a tool from the commandline... sorry for this much text...

    ReplyDelete
  6. I havent explored command line options till.I guess you should try whatever is given in that link.Also share your experience here.

    ReplyDelete
  7. @rourab : yes. you just have to understand the concept.you can make it for n bit. But the code will be more complicated.

    ReplyDelete
  8. function to_bcd ( bin : std_logic_vector((n-1) downto 0) ) return std_logic_vector is
    variable i : integer:=0;
    variable j : integer:=1;
    variable bcd : std_logic_vector(((4*q)-1) downto 0) := (others => '0');
    variable bint : std_logic_vector((n-1) downto 0) := bin;

    begin
    for i in 0 to n-1 loop -- repeating 8 times.
    bcd(((4*q)-1) downto 1) := bcd(((4*q)-2) downto 0); --shifting the bits.
    bcd(0) := bint(n-1);
    bint((n-1) downto 1) := bint((n-2) downto 0);
    bint(0) :='0';

    l1: for j in 1 to q loop
    if(i < n-1 and bcd(((4*q)-1) downto ((4*q)-4)) > "0100") then --add 3 if BCD digit is greater than 4.
    bcd(((4*q)-1) downto ((4*q)-4)) := bcd(((4*q)-1) downto ((4*q)-4)) + "0011";

    end if;

    end loop l1;
    end loop;
    return bcd;
    end to_bcd;

    ReplyDelete
  9. the previous code i have written in generic form ,where q is the number bcd digit,in that case i got the desire result up to 9 but when it exceed over 9 it gives the A,B,C,D,E,F. i cant get my mistake,

    ReplyDelete
  10. vipin i have solved my problem just replacing the q by j in the inner loop

    ReplyDelete
  11. rourab, please, can you tell me which q you replaced by j in the inner loop?

    I wrote similar code, but without generic parameter, and I had same problem (A,B,C..F).

    ReplyDelete
  12. function to_bcd ( bin : std_logic_vector((n-1) downto 0) ) return std_logic_vector is
    variable i : integer:=0;
    variable j : integer:=1;
    variable bcd : std_logic_vector(((4*q)-1) downto 0) := (others => '0');
    variable bint : std_logic_vector((n-1) downto 0) := bin;

    begin
    for i in 0 to n-1 loop -- repeating 8 times.
    bcd(((4*q)-1) downto 1) := bcd(((4*q)-2) downto 0); --shifting the bits.
    bcd(0) := bint(n-1);
    bint((n-1) downto 1) := bint((n-2) downto 0);
    bint(0) :='0';

    l1: for j in 1 to q loop
    if(i < n-1 and bcd(((4*j)-1) downto ((4*j)-4)) > "0100") then --add 3 if BCD digit is greater than 4.
    bcd(((4*j)-1) downto ((4*j)-4)) := bcd(((4*j)-1) downto ((4*j)-4)) + "0011";

    end if;

    end loop l1;
    end loop;
    return bcd;
    end to_bcd;



    this is my code

    ReplyDelete
  13. rourab,

    thank you very much, but I still have problem with this code, even when its generic.

    I wanted to convert 24-bit binary to 32-bit bcd and I inserted your function into my code, where I defined q := 8, because (4*q)-1) would be 32 bits.

    When I wanna do 8-bit bin to 12-bit bcd, I need to define q := 3.

    My question is:

    What is relation between n and q, what is relation between number of bits for input (bin) and number of bits for output (bcd)?

    Please, answer again...I am beginner and your little help is very great for me.

    Sory for my bad english, i hope that you can understand me.

    ReplyDelete
  14. I found answer for my question, but I need help for implementing that solution.

    Relation between n and q is :

    **********************************************
    q = round(n*(log(2)))

    where q must be rounded to greater integer !!!,
    **********************************************

    for example:

    for n = 24 and q = 8 it would be:

    q = round (32*(log (2)))= round (24*0.3010)=round (7.224)= 8


    I need help for implement this calculus for generic parameter (I want to make automatic calculus q = f(n)).

    Is this possible to be done with "real" data type,could we use not-synthesizable data type for generic parameter , to make synthesizable entity ?

    ReplyDelete
  15. how to implement for D-ALGORITHM IN TESTING OF VLSI CIRCUITS" IN VHDL OR VERILOG?"

    ReplyDelete
  16. Thanks, Men! That function helped me a lot. I made a physical implementation in a Spartan 3E using 8 switches as input and 3 displays as output. I wrote a post (Spanish) about it. I linked yours, of course!

    ReplyDelete
  17. Hi, I have the same question/problem which Aleksander mentioned above.

    ------------------------
    Is this possible to be done with "real" data type,could we use not-synthesizable data type for generic parameter , to make synthesizable entity ?
    ------------------------
    but, with a simpler implementation(from wiki): q = Roundup(n/3)

    Could anyone help me with their views please ??
    Thanks in advance.

    ReplyDelete
  18. Hello mate, I've written follow as your code above and my code is
    LIBRARY ieee;
    USE ieee.std_logic_1164.all;
    use IEEE.std_logic_arith.all;
    use IEEE.numeric_bit.all;
    use IEEE.numeric_std.all;
    use IEEE.std_logic_signed.all;

    ENTITY ten_to_bcd IS
    PORT ( b: IN std_logic_vector (7 downto 0) ; -- 10 bits input
    y: OUT std_logic_vector( 11 downto 0)); -- 16 bits output / 4 digits
    END ten_to_bcd;

    ARCHITECTURE double_d OF ten_to_bcd IS

    function dd( bin : std_logic_vector(7 downto 0) ) return std_logic_vector is
    variable i : integer:=0;
    variable bcd : std_logic_vector(11 downto 0) := "000000000000";
    variable bint : std_logic_vector(7 downto 0) := bin;
    variable kk : integer

    begin
    for i in 0 to 7 loop -- repeating 8 times.
    bcd(11 downto 1) := bcd(10 downto 0); --shifting the bits.
    bcd(0) := bint(7);
    bint(7 downto 1) := bint(6 downto 0);
    bint(0) :='0';


    if(i < 7 and bcd(3 downto 0) > "0100") then --add 3 if BCD digit is greater than 4.
    bcd(3 downto 0) := bcd(3 downto 0) + "0011";
    end if;

    if(i < 7 and bcd(7 downto 4) > "0100") then --add 3 if BCD digit is greater than 4.
    bcd(7 downto 4) := bcd(7 downto 4) + "0011";
    end if;

    if(i < 7 and bcd(11 downto 8) > "0100") then --add 3 if BCD digit is greater than 4.
    bcd(11 downto 8) := bcd(11 downto 8) + "0011";
    end if;

    end loop;
    return bcd;
    end dd;

    begin
    p:process(b)
    begin
    y <= dd(b);
    end process;
    END double_d;


    so, I have some problem like when I run the simulation and when it get thought 15 , the value doesn't change to 16 but it shows 10 as tbl file mentioned below

    0.0> 000 = 0000
    10.0> 001 = 0001
    20.0> 002 = 0002
    30.0> 003 = 0003
    40.0> 004 = 0004
    50.0> 005 = 0005
    60.0> 006 = 0006
    70.0> 007 = 0007
    80.0> 008 = 0008
    90.0> 009 = 0009
    100.0> 00A = 0010
    110.0> 00B = 0011
    120.0> 00C = 0012
    130.0> 00D = 0013
    140.0> 00E = 0014
    150.0> 00F = 0015
    160.0> 010 = 0010
    170.0> 011 = 0011
    180.0> 012 = 0012
    190.0> 013 = 0013
    200.0> 014 = 0020

    could you please figure out what wrong with my code T^T
    Thanksssss

    ReplyDelete
    Replies
    1. Hi, did you managed to figure it out?
      I am having the same issue.

      Thanks!

      Delete
  19. This is my generic implementation:

    function ToBcd(bin : word; nbrDecs : positive) return word is
    variable bcd : word(4*nbrDecs-1 downto 0);
    variable bint : word(bin'length-1 downto 0);
    variable i, j : integer;
    begin
    bint := bin;
    bcd := (others => '0');

    for i in 0 to bin'high loop
    bcd(bcd'high downto 1) := bcd(bcd'high-1 downto 0); -- Shift
    bcd(0) := bint(bin'high);
    --
    bint(bint'high downto 1) := bint(bint'high-1 downto 0); -- Shift
    bint(0) := '0';

    for j in 0 to nbrDecs-1 loop
    if (i < bin'high and bcd((j+1)*4-1 downto j*4) > "0100") then
    bcd((j+1)*4-1 downto j*4) := bcd((j+1)*4-1 downto j*4) + "0011";
    end if;
    end loop;
    end loop;
    return bcd;
    end function;

    ReplyDelete
  20. Working on a project that will be using a modified code for binary to dual bcd outs.

    Having some issued mapping the inputs to our variables.

    For example on input is marked as a and will be the MSB coming to the chipset.

    There have been attempts to work it up as vin(7)<= a;

    Would anyone be able to assist with explaining/helping/providing knowledge?

    Thank you,

    ReplyDelete
  21. The main problem with your codes, is that when : '' add 3 if BCD digit is greater than 4 '' you do not pay attention of the eventual reminder, indeed if you have 1110, you add 3 because it is greater than 4, it should give you 10001 but you just keep 0001.
    Sincerly,

    ReplyDelete
    Replies
    1. What's the proper way to do this? I am getting the same result as Sahacha Nilkumhang.

      Delete
  22. does the same rule of this algorithm work with 16 bit values?

    ReplyDelete
    Replies
    1. yes. it will work. its a generic algorithm. thought you have to rewrite the above code.

      Delete
  23. Hi guys,

    Could you provide a testbench for this design?
    Thank you very much.

    ReplyDelete
  24. Hi vipin
    Can I ask whether architecture of your vhdl code is behavioral or dataflow?

    ReplyDelete