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?
Literally translating your code to incorporate loops and more generic programming would yield something like this (note that this is untested):
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
andcarry(8) == 1
) lead to the else case. Not sure if this is what's intended for your design. To answer your question with code: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 onlyDATA_WIDTH / 8
instances because the generate loop goes from 0 toNUM_BYTES - 1
Yes, see the
generate for
statement and thefor
loop.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.