for-loopif-statementvhdlhdl

Elegant Way To Compress If/Elsif Statements into Single For Loop Statement in VHDL


First time posting, so forgive me for any syntax errors within stack overflows formatting. I am looking at a elegant way of compressing the following if/elsif code into a for loop. I have added some additional information at the start of the code for it to make a little more sense:

   -- all of this is declared within my modules package file.
   type uart_buffer is record
      tx_out_buf     : one_byte;                            -- Tx output UART Buffer.
      rx_out_buf     : one_byte;                            -- Rx output UART Buffer.      
      tx_out_buf_ful : std_logic;                           -- Indicate if the tx output buffer is full.
      rx_out_buf_ful : std_logic;                           -- Indicate if the rx output buffer is full.
      sending        : std_logic;                           -- Tx Output buffer is currently sending char.
   end record uart_buffer;

   type uart_arr is array(7 downto 0) of uart_buffer;

   constant EMPTY_UART_BUFFER : uart_buffer := (
      -- define all values of an empty UART Buffer
      tx_out_buf     => (others => '0'),
      rx_out_buf     => (others => '0'),
      tx_out_buf_ful => '0',
      rx_out_buf_ful => '0',
      sending        => '0'
      ); 


   -- This section is declared within my modules architecture.
   signal start_sending_char     : std_logic   := '0';  -- send a character packet to the device
   signal uart_buffer_arr        : uart_arr    := (others => EMPTY_UART_BUFFER);


   -- This section is within a process in my module that is conditioned to the 
   -- rising of edge of my systems clock. This is what I am hoping to simplify:
         if (start_sending_char = '0') then  
            if    (uart_buffer_arr(7).tx_out_buf_ful = '1') and
                  (uart_buffer_arr(7).sending = '0')
            then
               -- Send out to CHAR fifo from line 7.            
               uart_buffer_arr(7).sending <= '1';
               start_sending_char         <= '1';
            elsif (uart_buffer_arr(6).tx_out_buf_ful = '1') and
                  (uart_buffer_arr(6).sending = '0')
               -- Send out to CHAR fifo from line 6.
               uart_buffer_arr(6).sending <= '1';
               start_sending_char         <= '1';
            elsif (uart_buffer_arr(5).tx_out_buf_ful = '1') and
                  (uart_buffer_arr(5).sending = '0')
               -- Send out to CHAR fifo from line 5.                  
               uart_buffer_arr(5).sending <= '1';
               start_sending_char         <= '1';
            elsif (uart_buffer_arr(4).tx_out_buf_ful = '1') and
                  (uart_buffer_arr(4).sending = '0')
               -- Send out to CHAR fifo from line 4.                  
               uart_buffer_arr(4).sending <= '1';
               start_sending_char         <= '1';
            elsif (uart_buffer_arr(3).tx_out_buf_ful = '1') and
                  (uart_buffer_arr(3).sending = '0')
               -- Send out to CHAR fifo from line 3.                  
               uart_buffer_arr(3).sending <= '1';
               start_sending_char         <= '1';
            elsif (uart_buffer_arr(2).tx_out_buf_ful = '1') and
                  (uart_buffer_arr(2).sending = '0')
               -- Send out to CHAR fifo from line 2.                  
               uart_buffer_arr(2).sending <= '1';
               start_sending_char         <= '1';
            elsif (uart_buffer_arr(1).tx_out_buf_ful = '1') and
                  (uart_buffer_arr(1).sending = '0')
               -- Send out to CHAR fifo from line 1.
               uart_buffer_arr(1).sending <= '1';
               start_sending_char         <= '1';
            elsif (uart_buffer_arr(0).tx_out_buf_ful = '1') and
                  (uart_buffer_arr(0).sending = '0')
               -- Send out to CHAR fifo from line 0.
               uart_buffer_arr(0).sending <= '1';
               start_sending_char         <= '1';
            else
               -- No lines to send on.
               null;
            end if;
         end if;

Can that sequence of if/elsif statements be re-written to something like this in a for loop?

         for i in 7 downto 0 loop
            -- Send out to CHAR fifo starting with highest priority line 7 down to 0 
            -- if CHAR fifo is ready to receive.           
            if (start_sending_char = '0') then        
               if (uart_buffer_arr(i).tx_out_buf_ful = '1') and
                  (uart_buffer_arr(i).sending = '0')
               then 
                  uart_buffer_arr(i).sending <= '1';
                  start_sending_char         <= '1';
               end if;       
            end if;
         end loop; 

Solution

  • I agree to the comments of @user16145658 and @Jim Lewis. But I will give you an answer as this is a problem I have often be confronted to. Your nested elsif is a solution which has 3 disadvantages:

    When the numbers increase and you do not have to iterate from 0 to 7 but from 0 to 63 or bigger, then inserting a typo gets more probably and you have to simulate each branch to get sure, that everything works. This means a solution, which can be overlooked more easy (even if there are many branches) would be a better solution. I call such a solution "correct by construction".

    A nested elsif always causes deep timing paths. The reason is that all conditions must be checked one after the other (serial), because there is a priority order. The serial checking can cause problems reaching timing closure at synthesis, so it would be better to implement a solution which can check in parallel.

    The nested elsif makes it difficult to the reader, to check if in all branches the same action (only at another queue) is executed. This means, another person, who is not familiar with the design, has carefully to check each branch manually, in order to gain the knowledge if in each branch the same action is performed.

    Therefore I would rewrite your code in this way:

    process(uart_buffer_arr)
    begin
        for i in 7 downto 0 loop
            -- At all queues the same check is performed (correct by construction):
            send_request(i) <= uart_buffer_arr(i).tx_out_buf_ful and not uart_buffer_arr(i).sending;
        end loop;
    end process;
    process (clk)
    begin
        if rising_edge(clk) then
            if start_sending_char='0' then
                -- parallel check instead of a serial one (correct by construction):
                if send_request/=(send_request'range => '0') then
                    -- Short timing path for this signal:
                    start_sending_char <= '1';                                        
                end if;
            for i in 7 downto 0 loop
                -- nested "if" cannot be avoided here
                if send_request(i)='1' then
                    -- The same action is performed at all queues (correct by construction).
                    uart_buffer_arr(i).sending <= '1';
                    exit; -- provides priority order
                end if;
            end loop;
    
            -- ... additional code for resetting start_sending_char to 0
    
        end if;
    end process;