5
\$\begingroup\$

I'm implementing an AXI4-Stream module. The module uses three DSP blocks (DSP49E1, UG479 - Xilinx). In order to run the module at a frequency of 150 MHz, the design is a pipeline going successively through each DSP.

In this code, I made the choice of a for loop into a single process to implement the pipeline. I should say that I have simulated and tested this design on a Xilinx FPGA (7 series) and it works perfectly fine so far.

Would that be better to create independent processes instead of a for loop inside a single process?

I like the way I coded the pipeline because it saved me time (for the shift register) thanks to the for loop and the arrays of std_logic_vector.

However, is it a good way of coding it (frequency, power, FPGA utilization,...)? More generally, is it a good design practice or is it limiting for certain purposes? I would like to understand the whys and wherefores of my choice.

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

entity slave_AXIStream_RGBtoGray is
port (
    -- Main signals
    CLK                 : in std_logic;
    RESETN              : in std_logic;

    -- Ready signal for upstream block
    S_AXIS_TREADY       : out std_logic;
    -- Data in
    S_AXIS_TDATA        : in std_logic_vector(23 downto 0);
    -- Flag for first pixel of a frame
    S_AXIS_TUSER        : in std_logic;
    -- Flag for last pixel of a line
    S_AXIS_TLAST        : in std_logic;
   -- Valid data
   S_AXIS_TVALID       : in std_logic;

    -- Downstream blocks are ready
    M_AXIS_TREADY       : in std_logic;    
    -- Data out
    M_AXIS_TDATA        : out std_logic_vector(7 downto 0);
    -- Flag for first pixel of a frame
    M_AXIS_TUSER        : out std_logic;
    -- Flag for last pixel of a line
    M_AXIS_TLAST        : out std_logic;
    -- Valid data
    M_AXIS_TVALID       : out std_logic    
);
end slave_AXIStream_RGBtoGray;

architecture Behavioral of slave_AXIStream_RGBtoGray is

-- DSP to perform A*B+C 
COMPONENT dsp48E1_macro
    PORT (
        CLK : IN STD_LOGIC;
        CE : IN STD_LOGIC;
        SCLR : IN STD_LOGIC;
        A : IN STD_LOGIC_VECTOR(14 DOWNTO 0); -- signed, two's complement
        B : IN STD_LOGIC_VECTOR(14 DOWNTO 0); -- signed, two's complement
        C : IN STD_LOGIC_VECTOR(29 DOWNTO 0); -- signed, two's complement
        P : OUT STD_LOGIC_VECTOR(30 DOWNTO 0) -- signed, two's complement
    );
END COMPONENT;

-- main signals
signal main_ready               : std_logic;

-- DSP48E1 signals
signal dsp_A_out                : std_logic_vector(30 downto 0);    
signal dsp_B_out                : std_logic_vector(30 downto 0);
signal dsp_C_out                : std_logic_vector(30 downto 0);

-- Out = coef_red * component_red + coef_green * component_green + coef_blue * component_blue
-- sum of the factors <= 1
constant coef_red               : std_logic_vector(14 downto 0) := to_slv(to_ufixed (0.29, 8, -6));
constant coef_green             : std_logic_vector(14 downto 0) := to_slv(to_ufixed (0.59, 8, -6));
constant coef_blue              : std_logic_vector(14 downto 0) := to_slv(to_ufixed (0.11, 8, -6));

-- Pipeline
signal pipeline_step            : integer range 0 to 13;       
type array_component_2DSP is array(0 to 8) of std_logic_vector(7 downto 0);
type array_component_1DSP is array(0 to 4) of std_logic_vector(7 downto 0);
signal component_red_dsp        : std_logic_vector(7 downto 0);
signal component_green_dsp      : array_component_1DSP;
signal component_blue_dsp       : array_component_2DSP;
signal DV_step_dsp              : std_logic_vector(0 to 12); -- data valid
signal SOF_step_dsp             : std_logic_vector(0 to 12); -- start of frame
signal EOL_step_dsp             : std_logic_vector(0 to 12); -- end of line

-- Last step
signal component_mono_step_out  : std_logic_vector(7 downto 0);    
signal DV_step_out              : std_logic; -- data valid
signal SOF_step_out             : std_logic; -- start of frame
signal EOL_step_out             : std_logic; -- end of line

signal data_stored              : std_logic_vector(7 downto 0);
signal DV_stored                : std_logic; -- data valid
signal SOF_stored               : std_logic; -- start of frame
signal EOL_stored               : std_logic; -- end of line

begin

S_AXIS_TREADY <= main_ready;

M_AXIS_TDATA <= component_mono_step_out;
M_AXIS_TVALID <= DV_step_out;
M_AXIS_TUSER <= SOF_step_out;
M_AXIS_TLAST <= EOL_step_out;

DSP_A: dsp48E1_macro
PORT MAP (
    CLK => CLK,
    CE => main_ready,
    SCLR => not RESETN,
    A => '0' & component_red_dsp & "000000",
    B => coef_red,
    C => (others => '0'),
    P => dsp_A_out
);

DSP_B: dsp48E1_macro
PORT MAP (
    CLK => CLK,
    CE => main_ready,
    SCLR => not RESETN,
    A => '0' & component_green_dsp(4) & "000000",
    B => coef_green,
    C => dsp_A_out(29 downto 0),
    P => dsp_B_out
);

DSP_C: dsp48E1_macro
PORT MAP (
    CLK => CLK,
    CE => main_ready,
    SCLR => not RESETN,
    A => '0' & component_blue_dsp(8) & "000000",
    B => coef_blue,
    C => dsp_B_out(29 downto 0),
    P => dsp_C_out
);        

main_process: process(CLK)
begin
if (rising_edge (CLK)) then
if(RESETN = '0') then
    pipeline_step <= 0;
    main_ready <= '0';

    component_red_dsp <= (others => '0');
    component_green_dsp <= (others => (others => '0'));
    component_blue_dsp <= (others => (others => '0'));

    DV_step_dsp <= (others => '0');
    SOF_step_dsp <= (others => '0');
    EOL_step_dsp <= (others => '0');

    component_mono_step_out <= (others => '0');
    DV_step_out <= '0';
    SOF_step_out <= '0';
    EOL_step_out <= '0';

    DV_stored <= '0';
    SOF_stored <= '0';
    EOL_stored <= '0';
else

    FOR pipeline_step IN 0 TO 13 LOOP
        CASE pipeline_step IS
            WHEN 0 => 
                if main_ready = '1' then
                    component_red_dsp <= S_AXIS_TDATA(23 downto 16);
                    component_green_dsp(pipeline_step) <= S_AXIS_TDATA(15 downto 8);
                    component_blue_dsp(pipeline_step) <= S_AXIS_TDATA(7 downto 0);
                    DV_step_dsp(pipeline_step) <= S_AXIS_TVALID;
                    SOF_step_dsp(pipeline_step) <= S_AXIS_TUSER;
                    EOL_step_dsp(pipeline_step) <= S_AXIS_TLAST;
                end if;

            WHEN 1 to 4 => 
                if main_ready = '1' then
                    component_green_dsp(pipeline_step) <= component_green_dsp(pipeline_step-1);
                    component_blue_dsp(pipeline_step) <= component_blue_dsp(pipeline_step-1);
                    DV_step_dsp(pipeline_step) <= DV_step_dsp(pipeline_step-1); 
                    SOF_step_dsp(pipeline_step) <= SOF_step_dsp(pipeline_step-1);
                    EOL_step_dsp(pipeline_step) <= EOL_step_dsp(pipeline_step-1);
                end if;            

            WHEN 5 to 8 => 
                if main_ready = '1' then
                    component_blue_dsp(pipeline_step) <= component_blue_dsp(pipeline_step-1);
                    DV_step_dsp(pipeline_step) <= DV_step_dsp(pipeline_step-1); 
                    SOF_step_dsp(pipeline_step) <= SOF_step_dsp(pipeline_step-1);
                    EOL_step_dsp(pipeline_step) <= EOL_step_dsp(pipeline_step-1);
                end if;

            WHEN 9 to 12 => 
                if main_ready = '1' then
                    DV_step_dsp(pipeline_step) <= DV_step_dsp(pipeline_step-1); 
                    SOF_step_dsp(pipeline_step) <= SOF_step_dsp(pipeline_step-1);
                    EOL_step_dsp(pipeline_step) <= EOL_step_dsp(pipeline_step-1); 
                end if;                     

            WHEN 13 =>
                if M_AXIS_TREADY = '1' or DV_step_out = '0' then
                    main_ready <= '1';
                    DV_step_out <= '0';

                    if main_ready = '1' then
                        component_mono_step_out <= dsp_C_out(19 downto 12); 
                        DV_step_out <= DV_step_dsp(pipeline_step-1);
                        SOF_step_out <= SOF_step_dsp(pipeline_step-1);
                        EOL_step_out <= EOL_step_dsp(pipeline_step-1); 
                    else
                        component_mono_step_out <= data_stored; 
                        DV_step_out <= DV_stored;
                        SOF_step_out <= SOF_stored;
                        EOL_step_out <= EOL_stored;  
                        DV_stored <= '0';
                    end if;
                else
                    main_ready <= '0';

                    if main_ready = '1' then
                        data_stored <= dsp_C_out(19 downto 12);
                        DV_stored <= DV_step_dsp(pipeline_step-1);
                        SOF_stored <= SOF_step_dsp(pipeline_step-1);
                        EOL_stored <= EOL_step_dsp(pipeline_step-1); 
                    end if;
                end if;

            WHEN others => NULL;
        END CASE;
    END LOOP;
end if;
end if;
end process;

end Behavioral;
\$\endgroup\$
5
\$\begingroup\$

I prefer generate statements over complex VHDL processes. Process descriptions won't unveil pipeline wiring faults, because a process can't create multiple drivers (last assignment wins). In a generate description, such a fault creates multiple driver which can be detected by the tools (synthesis and simulation).

You could try to write generic VHDL code for your MACC operation. If Xilinx ISE can infer the correct hardware (so it uses the DSP48E* hard macro and the embedded adder), then it's a better long term solution. Each FPGA generation and family has its own DSPxxEy hard macros. So using generic VHDL code can increase maintainability and portability. (On the other hand synthesis tools are known for unlearning ... )

The component syntax is 'outdated'. You can spare the component declaration and use this line for the instantiation:

DSP_A: entity work.dsp48E1_macro
  port map (
    -- ...
  );

If the macro is compiled into another library than the current design unit (work) is compiled into, then replace work with the correct library name.

You might want to replace some of your magic numbers (12, 13, 14, 19, 30, ...) with constants or generic parameters and reuse or calculate them. So if you decide to increase ranges, it's easier to modify only a few constants rather than rethinking the complete algorithm.


Appendix - generate examples

Here are two generate example, which I wrote:

  1. An Odd-Even Sort sorting network. It has a generate loop for the stages and a simple odd/even decision to instantiate the correct stage element.

  2. An Odd-Even Mergesort sorting network. It has 5 nested generate loops to translate the recursive (software) description into a linear one. While doing so, I found that the proposed software algorithm is not "good". It's correct but is produces more compare operations than required.
    Constructing an odd-even mergesort sorting network

    Without generate statements and a non-Xilinx tool, I had never found out that my design has multiple drivers. Some tools don't create a multiple driver warning if all drivers generate the same value. In simulation, this fault is also undetected because equal driving drivers resolve to the same value.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.