VHDL: Designing an arithmetic unit with MMX x86 instructions for operand sizes from 64 to 8 bits

63 views Asked by At

I'm working on a VHDL project where I need to design an arithmetic unit that executes 6 MMX x86 instructions capable of handling operand sizes ranging from 64 to 8 bits. I started with the PADD instructions and I've implemented an 8-bit ripple-carry adder and cascaded them to achieve the desired data width. The challenge I'm facing is making the design adaptable to various operand sizes without having to manually modify instantiation statements.

I've tried to use generate statements to instantiate the ripple-carry adders based on the DATA_WIDTH generic, but I'm encountering issues with indexing and adapting the multiplexer logic accordingly.

Here is the code from my PADD component:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity PADD is
    generic (
        DATA_WIDTH : integer := 64
    );
    Port (
        Cin, clk, reset, enable : in std_logic;
        op1, op2 : in std_logic_vector(DATA_WIDTH - 1 downto 0);
        result : out std_logic_vector(DATA_WIDTH - 1 downto 0);
        Cout : out std_logic
    );  
end PADD;

architecture Behavioral of PADD is
    signal carry : std_logic_vector(7 downto 0);
    signal S : std_logic_vector(DATA_WIDTH - 1  downto 0);

    component full_adder
        Port (A, B, Cin : in std_logic; 
              S, Cout : out std_logic);
    end component;

    component ripple_carry_adder_padd
    Port (
        A, B    : in  std_logic_vector(7 downto 0);
        Cin     : in  std_logic;
        S       : out std_logic_vector(7 downto 0);
        Cout    : out std_logic
        );
    end component;
    
    signal mux_res : std_logic_vector(DATA_WIDTH - 1 downto 0);

begin
    
    carry(0) <= '0';

    RCA_1 : ripple_carry_adder_padd port map(A => op1(7 downto 0), B => op2(7 downto 0), Cin => carry(0), S => S(7 downto 0), Cout => carry(1));
    RCA_2 : ripple_carry_adder_padd port map(A => op1(15 downto 8), B => op2(15 downto 8), Cin => carry(1), S => S(15 downto 8), Cout => carry(2));
    RCA_3 : ripple_carry_adder_padd port map(A => op1(23 downto 16), B => op2(23 downto 16), Cin => carry(2), S => S(23 downto 16), Cout => carry(3));
    RCA_4 : ripple_carry_adder_padd port map(A => op1(31 downto 24), B => op2(31 downto 24), Cin => carry(3), S => S(31 downto 24), Cout => carry(4));
    RCA_5 : ripple_carry_adder_padd port map(A => op1(39 downto 32), B => op2(39 downto 32), Cin => carry(4), S => S(39 downto 32), Cout => carry(5));
    RCA_6 : ripple_carry_adder_padd port map(A => op1(47 downto 40), B => op2(47 downto 40), Cin => carry(5), S => S(47 downto 40), Cout => carry(6));
    RCA_7 : ripple_carry_adder_padd port map(A => op1(55 downto 48), B => op2(55 downto 48), Cin => carry(6), S => S(55 downto 48), Cout => carry(7));
    RCA_8 : ripple_carry_adder_padd port map(A => op1(63 downto 56), B => op2(63 downto 56), Cin => carry(7), S => S(63 downto 56), Cout => Cout);
   
    
    MUX_process: process(carry, S)
    begin
        if carry(1) = '1' then
            mux_res <= "00000000000000000000000000000000000000000000000000000000" & S(DATA_WIDTH - 1 downto DATA_WIDTH - 8); --7 downto 0
        elsif carry(2) = '1' then
            mux_res <= "000000000000000000000000000000000000000000000000" & S(DATA_WIDTH - 1 downto DATA_WIDTH - 16); -- 15 downto 0
        elsif carry(3) = '1' then
            mux_res <= "0000000000000000000000000000000000000000" & S(DATA_WIDTH - 1 downto DATA_WIDTH - 24); --23 downto 0
        elsif carry(4) = '1' then  
            mux_res <= "00000000000000000000000000000000" & S(DATA_WIDTH - 1 downto DATA_WIDTH - 32); --31 dowto 0
        elsif carry(5) = '1' then
            mux_res <= "000000000000000000000000" & S(DATA_WIDTH - 1 downto DATA_WIDTH - 40); --39 downto 0
        elsif carry(6) = '1' then
            mux_res <= "0000000000000000" & S(DATA_WIDTH - 1 downto DATA_WIDTH - 48); --47 downto 0
        elsif carry(7) = '1' then
            mux_res <= "00000000" & S(DATA_WIDTH - 1 downto DATA_WIDTH - 56); -- 55 downto 0
        else
            mux_res <= S(DATA_WIDTH - 1 downto DATA_WIDTH - 64);
        end if;
    end process;
    
    result <= mux_res;

end Behavioral;

My questions are:

1.How can I modify the instantiation logic to ensure it works seamlessly for operand sizes from 64 to 4 bits? 2.Is there a better approach to handling different operand sizes without duplicating instantiation statements? 3.How should I adapt the multiplexer logic to accommodate varying operand sizes?

1

There are 1 answers

0
Schottky On

Literally translating your code to incorporate loops and more generic programming would yield something like this (note that this is untested):

architecture Behavioral of PADD is
    constant NUM_BYTES : natural := DATA_WIDTH / 8;

    signal carry : std_logic_vector(NUM_BYTES downto 0);
    signal S : std_logic_vector(DATA_WIDTH - 1  downto 0);

    component ripple_carry_adder_padd
    Port (
        A, B    : in  std_logic_vector(7 downto 0);
        Cin     : in  std_logic;
        S       : out std_logic_vector(7 downto 0);
        Cout    : out std_logic
        );
    end component;
    
    signal mux_res : std_logic_vector(DATA_WIDTH - 1 downto 0);

begin
    
    carry(0) <= '0';

    rc_chain : for i in 0 to NUM_BYTES - 1 generate
        RC_int : ripple_carry_adder_padd 
        port map(
            A => op1(i * 8 - 1 downto (i - 1) * 8),
            B => op2(i * 8 - 1 downto (i - 1) * 8),
            Cin => carry(i),
            S => S(i * 8 - 1 downto (i - 1) * 8),
            Cout => carry(i + 1)
        );
    end generate;

    Cout <= carry(NUM_BYTES);

    MUX_process: process(carry, S)
    begin
        if unsigned(carry) = 0 then
            mux_res <= S(DATA_WIDTH - 1 downto DATA_WIDTH - 64);
        else
            for i in 1 to NUM_BYTES loop
                if carry(i) = '1' then
                    mux_res(mux_res'high downto i * 8) <= (others => '0');
                    mux_res(i * 8 - 1 downto 0) <= S(DATA_WIDTH - 1 downto DATA_WIDTH - i * 8);
                    exit;
                end if;
            end loop;
        end if;
    end process;

    result <= mux_res;

end Behavioral;

I'm not quite sure about the mix process though. Note the weird if check you'd have to do to literally get what you have written in your other code. This is needed because both cases (cary == 0 and carry(8) == 1) lead to the else case. Not sure if this is what's intended for your design. To answer your question with code:

  1. See the constant NUM_BYTES on top. I assume that you mean from 64 to 8 bits in 8 bit steps since this is what you have written on top and your adder only takes 8 bit inputs. The code will generate only DATA_WIDTH / 8 instances because the generate loop goes from 0 to NUM_BYTES - 1

  2. Yes, see the generate for statement and the for loop.

  3. Same answer, see the for loop.

Note that this code likely doesn't work out of the box as it's untested. However, the idea of using loops in processes and for generate statements outside is what you need for the generic aspect of your problem.