VHDL Arithmetic Functions

I’m currently working on an ALU in VHDL.  To get warmed up, I tried my hand at some basic arithmetic, which I’m going to discuss in this blog post.  Here’s the diagram of the ALU:

A and B are the two inputs and F will be the result.  Each input and output is a 4-bit number.  S represents the function selected.  For now I’m going to use the following:

  • 1 = Add
  • 2 = Subtract
  • 3 = Multiply
  • 4 = Divide

I can add new functions as I need them.  I also have the option of expanding the number of bits to work with.  For now, I’ll just keep this simple.

I’m a software developer, so I look at this problem and I think “switch/case statement”.  As it turns out, there is a case statement for VHDL.  Some searching on Bing turns up this website: VHDL-Online.  Which I found to be very clear and easy to read.  I learn quicker from examples, so this site will be my go-to site for looking up VHDL syntax.  As I looked over the syntax examples, I noticed that the case statement doesn’t work without a process block.  I just wrapped my case statement with a generic process block and came up with this block of code:

entity smallalu is port (
S: in integer range 0 to 15;
A,B: in signed(3 DOWNTO 0);
F: out signed(3 DOWNTO 0)
);
end smallalu;

architecture Behavioral of smallalu is
begin
    process (S,A,B)
    begin
        case S is
            when 1 => 
                F <= A+B;
            when 2 =>
                F <= A-B;
            when 3 =>
                F <= RESIZE(A*B,4);
            when 4 =>
                F <= A/B;
            when others =>
                
        end case;
    end process;
end Behavioral;

You’ll have to include “use IEEE.numeric_std.ALL;” at the top in order to use the math functions (and “signed” data types).  Most of the code is pretty obvious: When the selector is set to “1”, then add the two inputs and assign to the output and so on.  The multiply was a bit of a challenge.  My simulation was showing “U” for the outputs of a multiply.  I did some investigating and discovered (or rather, rediscovered) that multiplying two 4-bit numbers results in an 8-bit number.  At one time, I knew that, but it’s been a while.  So I did some research and discovered the “resize” function that allowed me to take the 8-bit result and resize to a 4-bit result.  The understanding is that I can’t really multiple any more than 2-bits from A with 2-bits from B, otherwise, it’ll overflow.  So I’ll need to figure out a solution to that issue in the future, when I decided to expand the data path width.

There is also an unsigned data type.  If you change your inputs to unsigned, you must also change F to unsigned.  Everything will work correctly (except you’ll be working with positive numbers only).

Next, I wanted to add a reset or clear function.  Technically, it’s just a constant zero output because there are not latches inside this ALU.  This code is pure logic.  Here is what the code looks like after the change:

entity smallalu is port (
S: in integer range 0 to 15;
A,B: in signed(3 DOWNTO 0);
F: out signed(3 DOWNTO 0)
);
end smallalu;

architecture Behavioral of smallalu is
begin
    process (S,A,B)
    begin
        case S is
            when 0 =>
                F <= to_signed(0,4);
            when 1 => 
                F <= A+B;
            when 2 =>
                F <= A-B;
            when 3 =>
                F <= RESIZE(A*B,4);
            when 4 =>
                F <= A/B;
            when others =>
                
        end case;
    end process;
end Behavioral;

As you can see, assigning a zero to F is not just a matter of using an assignment.  A constant zero is assumed to be an integer data type.  The “to_signed()” function can be used to convert it into a signed data type.  This function requires the number of bits, so I put in a 4.  The simulation look like this:

The first block up to 10ns is just the clear.  From 10ns to 20ns is “add”, from 20-30 is “subtract”, 30-40 is multiply and finally 40-50 is divide (as you can see I’m dividing 4 by 2).

One last test, I decided to compile this code for the mimas board, just to see what kind of resources it would occupy on my FPGA.  I didn’t map any inputs and outputs, and I didn’t transfer this to the board since I don’t have enough dip switches to represent S, A and B (though I’m sure I could get creative and use the push buttons for “B” inputs or something).  Anyway, here is the result:

Slice Logic Utilization:
  Number of Slice Registers:                     0 out of  11,440    0%
  Number of Slice LUTs:                         47 out of   5,720    1%
    Number used as logic:                       47 out of   5,720    1%
      Number using O6 output only:              38
      Number using O5 output only:               0
      Number using O5 and O6:                    9
      Number used as ROM:                        0
    Number used as Memory:                       0 out of   1,440    0%

Slice Logic Distribution:
  Number of occupied Slices:                    19 out of   1,430    1%
  Number of MUXCYs used:                         8 out of   2,860    1%
  Number of LUT Flip Flop pairs used:           47
    Number with an unused Flip Flop:            47 out of      47  100%
    Number with an unused LUT:                   0 out of      47    0%
    Number of fully used LUT-FF pairs:           0 out of      47    0%
    Number of slice register sites lost
      to control set restrictions:               0 out of  11,440    0%

As you can see 47 LUTs are used for the logic as well as 19 slices.  This represents about 1% of the chip resources.  Not bad.  I’m betting that a multiplier scales up exponentially.  So an 8-bit alu is going to take up more than double the resources.  Let’s find out…

Slice Logic Utilization:
  Number of Slice Registers:                     0 out of  11,440    0%
  Number of Slice LUTs:                        112 out of   5,720    1%
    Number used as logic:                      112 out of   5,720    1%
      Number using O6 output only:             101
      Number using O5 output only:               0
      Number using O5 and O6:                   11
      Number used as ROM:                        0
    Number used as Memory:                       0 out of   1,440    0%

Slice Logic Distribution:
  Number of occupied Slices:                    42 out of   1,430    2%
  Number of MUXCYs used:                        32 out of   2,860    1%
  Number of LUT Flip Flop pairs used:          112
    Number with an unused Flip Flop:           112 out of     112  100%
    Number with an unused LUT:                   0 out of     112    0%
    Number of fully used LUT-FF pairs:           0 out of     112    0%
    Number of slice register sites lost
      to control set restrictions:               0 out of  11,440    0%

Hmmm…. Only a little over double (2.38 x).  Time to setup a multiply only and see what resources it takes to multiply two numbers together.  Here’s my basic code:

entity multiplier is port (
A,B: in signed(3 DOWNTO 0);
Y: out signed(3 DOWNTO 0)
);
end multiplier;

architecture Behavioral of multiplier is
    
begin
    Y <= RESIZE(A*B,4);
end Behavioral;
Slice Logic Utilization:
  Number of Slice Registers:                     0 out of  11,440    0%
  Number of Slice LUTs:                         15 out of   5,720    1%
    Number used as logic:                       15 out of   5,720    1%
      Number using O6 output only:              10
      Number using O5 output only:               0
      Number using O5 and O6:                    5
      Number used as ROM:                        0
    Number used as Memory:                       0 out of   1,440    0%

That’s 15 LUTs to multiply two 4-bit numbers together.  8-bit numbers:

Device Utilization Summary:

Slice Logic Utilization:
  Number of Slice Registers:                     0 out of  11,440    0%
  Number of Slice LUTs:                          0 out of   5,720    0%

Slice Logic Distribution:
  Number of occupied Slices:                     0 out of   1,430    0%
  Number of MUXCYs used:                         0 out of   2,860    0%
  Number of LUT Flip Flop pairs used:            0

IO Utilization:
  Number of bonded IOBs:                        24 out of     200   12%

Specific Feature Utilization:
  Number of RAMB16BWERs:                         0 out of      32    0%
  Number of RAMB8BWERs:                          0 out of      64    0%
  Number of BUFIO2/BUFIO2_2CLKs:                 0 out of      32    0%
  Number of BUFIO2FB/BUFIO2FB_2CLKs:             0 out of      32    0%
  Number of BUFG/BUFGMUXs:                       0 out of      16    0%
  Number of DCM/DCM_CLKGENs:                     0 out of       4    0%
  Number of ILOGIC2/ISERDES2s:                   0 out of     200    0%
  Number of IODELAY2/IODRP2/IODRP2_MCBs:         0 out of     200    0%
  Number of OLOGIC2/OSERDES2s:                   0 out of     200    0%
  Number of BSCANs:                              0 out of       4    0%
  Number of BUFHs:                               0 out of     128    0%
  Number of BUFPLLs:                             0 out of       8    0%
  Number of BUFPLL_MCBs:                         0 out of       4    0%
  Number of DSP48A1s:                            1 out of      16    6%
  Number of ICAPs:                               0 out of       1    0%
  Number of MCBs:                                0 out of       2    0%
  Number of PCILOGICSEs:                         0 out of       2    0%
  Number of PLL_ADVs:                            0 out of       2    0%
  Number of PMVs:                                0 out of       1    0%
  Number of STARTUPs:                            0 out of       1    0%
  Number of SUSPEND_SYNCs:                       0 out of       1    0%

Well, that’s interesting.  Apparently, there are 16 DSP modules and one of those was used for an 8-bit multiplier.  The same results from a 16-bit multiplier.  Let’s push it a little.  Here’s a 32-bit multiplier:

Specific Feature Utilization:
  Number of RAMB16BWERs:                         0 out of      32    0%
  Number of RAMB8BWERs:                          0 out of      64    0%
  Number of BUFIO2/BUFIO2_2CLKs:                 0 out of      32    0%
  Number of BUFIO2FB/BUFIO2FB_2CLKs:             0 out of      32    0%
  Number of BUFG/BUFGMUXs:                       0 out of      16    0%
  Number of DCM/DCM_CLKGENs:                     0 out of       4    0%
  Number of ILOGIC2/ISERDES2s:                   0 out of     200    0%
  Number of IODELAY2/IODRP2/IODRP2_MCBs:         0 out of     200    0%
  Number of OLOGIC2/OSERDES2s:                   0 out of     200    0%
  Number of BSCANs:                              0 out of       4    0%
  Number of BUFHs:                               0 out of     128    0%
  Number of BUFPLLs:                             0 out of       8    0%
  Number of BUFPLL_MCBs:                         0 out of       4    0%
  Number of DSP48A1s:                            4 out of      16   25%
  Number of ICAPs:                               0 out of       1    0%
  Number of MCBs:                                0 out of       2    0%
  Number of PCILOGICSEs:                         0 out of       2    0%
  Number of PLL_ADVs:                            0 out of       2    0%
  Number of PMVs:                                0 out of       1    0%
  Number of STARTUPs:                            0 out of       1    0%
  Number of SUSPEND_SYNCs:                       0 out of       1    0%

No LUTs were used, but 4 DSPs were used.  For a 64-bit multiplier:

ERROR:Place:543 – This design does not fit into the number of slices available

Darn!  I had high-hopes.  Oh well.  Now we know a limit to the Spartan-6 XC6SLX9 FPGA chip.

One other arithmetic function available is the modulo (mod).  Which gives the remainder.  Let’s add that to the ALU:

entity smallalu is port (
S: in integer range 0 to 15;
A,B: in signed(7 DOWNTO 0);
F: out signed(7 DOWNTO 0)
);
end smallalu;

architecture Behavioral of smallalu is
begin
    process (S,A,B)
    begin
        case S is
            when 0 =>
                F <= to_signed(0,8);
            when 1 => 
                F <= A+B;
            when 2 =>
                F <= A-B;
            when 3 =>
                F <= RESIZE(A*B,8);
            when 4 =>
                F <= A/B;
            when 5 =>
                F <= A mod B;
            when others =>
                
        end case;
    end process;
end Behavioral;

As you can see from the simulation, 7/2 gives a remainder of 1:

Finally, here’s 8/2:

 

Leave a Reply