Cortando recursos

A cada dia os avanços tecnológicos colocam a disposição do projetista novos componentes e todo o mundo faz de tudo para estar no topo da onda. Os componentes eletrônicos fazem mais coisas e mais rápido e, as vezes, com mais eficiência; isto também tem sua parte negativa. Mais e melhores componentes também conseguem ser usados ineficientemente com mais facilidade. A arte de fazer com eficiência é cada vez mais rara nos projetos eletrônicos. Infelizmente, os atuais níveis de abstração usados em projeto com lógica programável mascaram geralmente isto. O projetista fica cada vez mais afastado da implementação física e tem a tendência de não atentar para o que está fazendo, que é descrever o comportamento de um circuito eletrônico.

Com isto em mente iremos fazer um projeto simples em FPGA para implementar um dos blocos básicos em DSP, um filtro médio móvel. São filtros muito simples, mas não por isso pouco eficientes se usados e implementados da forma correta. Às vezes, os projetistas subestimam estes filtros pela sua simplicidade, mas para algumas aplicações estão próximos do ideal.

A ideia por trás do filtro é adicionar uma série de amostras no entorno da amostra que querermos filtrar para reduzir o ruído ou dispersão da amostra.

A função que descreveria o filtro seria :

formula

A tarefa será procurar uma solução em lógica programável que forneça um resultado matematicamente equivalente.


Implementando para hardware


Criar a implementação de um algoritmo em hardware significa implementar pensando nos recursos disponíveis no hardware.

Para o caso as amostras poderiam ser salvas em registros e as adições seriam implementadas nos geradores de funções da arquitetura de lógica programável selecionada.

Diagrama de bloques 0 Vamos, por enquanto, não considerar a divisão e desenhar o dividendo da equação do filtro como um diagrama de blocos. O diagrama seria uma série de registros com as correspondentes adições. Olhando no desenho já evidenciamos um potencial problema, se a formula do filtro for implementada em hardware poderia criar muitos níveis de lógica.

Diagrama de bloques 1 Cada adição seria implementada num nível lógico. No exemplo iríamos ter 3 níveis de lógica o que reduziria a frequência máxima de operação do filtro pelos tempos de propagação.

Diagrama de bloques 3 Podermos melhorar isto. Analisemos as adições conforme chegam as amostras; um certo número de amostras permanecem constantes.
Poderíamos, portanto, salvar o valor já somado dessas amostras e usar mais de uma vez sem que seja necessário fazer todas as adições a cada nova amostra.

Resumindo, para cada nova amostra adicionaríamos uma amostra e subtrairíamos outra.

Diagrama de vloques 4 Reduziremos nosso diagrama para apenas duas operações; uma adição e uma subtração.
Comparando opções, esta versão tem a mais o diferencial que a máxima frequência de operação seria independente do número de passos do filtro. O diagrama adiciona também um acumulador para manter a conta, “A”.

Esta arquitetura apresenta mais um beneficio adicional. Montando um multiplexor nas saídas dos registros implementaríamos um filtro Moving Average de comprimento variável dinamicamente.

Diagrama de bloques 6 Mais uma vez, redesenhamos o diagrama de blocos para simplificar. N seria o ajuste do número de passos.


Descrevendo hardware


Vamos descrever em VHDL a arquitetura que terminarmos de criar, iniciáramos com a interconexão.

   ENTITY MAF IS
      PORT(
         CLOCK : IN  STD_LOGIC;
             N : IN  STD_LOGIC_VECTOR( 4 DOWNTO 0);
            DI : IN  STD_LOGIC_VECTOR(15 DOWNTO 0);
            DO : OUT STD_LOGIC_VECTOR(15 DOWNTO 0)
      );
   END MAF;

A seguir descrevermos as estruturas de dados; definirmos um tipo de dados ARRAY com comprimento 2N-1 e o numero de bits igual ao range do dados de entrada.

   TYPE sDesc IS ARRAY (0 TO (2**N'length)-1) OF STD_LOGIC_VECTOR(DI'RANGE);
   SIGNAL s : sDesc := (OTHERS => (OTHERS => '0'));
   SIGNAL a : SIGNED(DI'length + N'length DOWNTO 0) := (OTHERS => '0');

“s” seria o registro das amostras e “a” o acumulador conforme o desenho.

Resta descrever a função de registro de deslocamento que seria :

            s <= DI & s(0 TO s'right-1);
         

e o acumulador com as adiciones e subtrações

            a <= a + SIGNED(DI) - SIGNED(s(to_integer(UNSIGNED(N))));
         

Finalmente o processo que descreve o “MovingAverageFilter” seria :

   MovingAverageFilter : PROCESS(CLOCK)
   BEGIN
      IF rising_edge(CLOCK) THEN 
         s <= DI & s(0 TO s'right-1);  
         a <= a + SIGNED(DI) - SIGNED(s(to_integer(UNSIGNED(N)))); 
      END IF;
   END PROCESS MovingAverageFilter;	

Verificação funcional


Para verificar a funcionalidade criamos o banco de teste do filtro no simulador.

Os estímulos do banco de teste seriam clock, n e di; do seria o dado retornado pelo filtro. No dado de entrada adicionamos ruído nas amostras para conseguir uma SNR de 25dB, isto para aproximar uma amostragem real. Quantificarmos em 16 bit, ponto fixo para uma dinâmica de -1,0 ; 1,0.

Dependendo da nossa ferramenta a apresentação de resultados poderia ser evidente. Se ajustarmos as propriedades de apresentação alineados com tipo de dado as formas de onda de entrada, saída e acumulador seriam :

Simulacion 1

Dando zoom :

Simulacion 2

Dá para visualizar nas formas de ondas que o filtro estaria funcionado como esperado; os dados de entrada tem mais ruído que os dados de saída.

O código completo do filtro ajustável seria

-------------------------------------------------------------------------------
--
--                                                       walter d. gallegos
--                                                   www.waltergallegos.com
--                                             Programable Logic & Software
--                                                     Consultoria y Diseno
--
-- Este archivo y documentacion son propiedad intelectual de Walter D. Gallegos
--
-------------------------------------------------------------------------------

LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL, IEEE.NUMERIC_STD.ALL;

ENTITY MAF IS
   PORT(
      CLOCK : IN  STD_LOGIC;
          N : IN  STD_LOGIC_VECTOR( 4 DOWNTO 0);
         DI : IN  STD_LOGIC_VECTOR(15 DOWNTO 0);
         DO : OUT STD_LOGIC_VECTOR(15 DOWNTO 0));
END MAF;

ARCHITECTURE REV0 OF MAF IS                                 
                                 
   TYPE sDesc IS ARRAY (0 TO (2**N'length)-1) OF STD_LOGIC_VECTOR(DI'RANGE);
   SIGNAL s : sDesc; 
   SIGNAL a : SIGNED(DI'length + N'length -1 DOWNTO 0);
   SIGNAL d : SIGNED(DO'RANGE);
   
BEGIN   
   
   MovingAverageFilter : PROCESS(CLOCK)
   BEGIN
      IF rising_edge(CLOCK) THEN  
         s <= DI & s(0 TO s'right-1); 
         a <= a + SIGNED(DI) - SIGNED(s(to_integer(UNSIGNED(N)))); 
      END IF;
   END PROCESS MovingAverageFilter;	
   
   IntDiv : d <=  a(a'left   DOWNTO a'left-DO'left)   WHEN N = "11111" ELSE 
                  a(a'left-1 DOWNTO a'left-DO'left-1) WHEN N = "01111" ELSE 
                  a(a'left-2 DOWNTO a'left-DO'left-2) WHEN N = "00111" ELSE 
                  a(a'left-3 DOWNTO a'left-DO'left-3) WHEN N = "00011" ELSE 
                  a(a'left-4 DOWNTO a'left-DO'left-4);
					                  
   DO <= STD_LOGIC_VECTOR(d);
   
END REV0;

O multiplexador IntDiv implementa a divisão deslocando de um bit baseado no valor do N.

As formas de onda a seguir apresentam a entrada e saída do filtro mudando o número de passos, experimentando uma verificação rápida do funcionamento do mux, o range de saída permanece dentro da mesma faixa e como esperado o ruído na saída acrescentou ao diminuir os passos.

Simulación con 7 pasos

A Implementação física


Tools Flow No funcional, nosso filtro estaria dando os resultados esperados. Vamos passar na implementação física. Até este momento o projeto foi genérico quanto aos componentes e as ferramentas; poderia ser implementado sob quaisquer componente de lógica programável.

De fato, uma vez ajustado o fluxo de implementação do fornecedor de FPGA na ferramenta, e isto fica fora do escopo desta nota, usar um ou outro fornecedor traz pouca diferença no processo, não é assim nos resultados; que seria fruto dos recursos disponíveis no componente e do uso que fizermos deles no código HDL.

Vamos, portanto, apenas conferir se os resultados da implementação coincidem com o esperado na descrição feita em VHDL para duas arquiteturas diferentes.

Deveríamos esperar 21 flip-flops para o acumulador mais 512 flip-flops para salvar as amostras, isto pelo código VHDL.

A implementação para a arquitetura um resulta em 533 registros. Está excelente, é exatamente o esperado. Mas alterando o componente, a implementação para uma outra arquitetura de FPGA mostra 21 registros, ou seja, o mesmo código sob uma arquitetura diferente da um resultado extraordinariamente diferente.

Procurando a fonte de tal diferença vamos analisar a informação dos reports da ferramenta para a segunda arquitetura,além dos 21 registros temos 78 LUT que por sua vez são usados em duas formas diferentes, 62 delas são usadas nos geradores de funções e as 16 restantes como memória.

Os registros para as amostras não seriam implementados em Flip-Flops mas em LUTs usando um recurso de uma arquitetura particular; aliás, o multiplexado realmente não consume recursos adicionais, faz parte da mesma LUT usada para salvar as amostras.

Diagrama de bloques 8 Mas isto não é fruto da magia obscura; é fruto da técnica, o código VHDL foi escrito conhecendo a particularidade desta arquitetura de lógica programável em particular.

Por tanto, um código VHDL genérico poderia ser implementado para quaisquer arquiteturas de lógica programável; mas se descrevermos nossa funcionalidade cuidadosamente poderíamos fazer uma descrição que, caso a arquitetura suportar, a ferramenta de implementação conseguiria otimizar usando os recursos disponíveis na arquitetura.

Fica uma pergunta no ar : Qual seria o resultado se não tivéssemos pensado um pouco no que poderia ser otimizado para a arquitetura selecionada antes de descrever em VHDL ?

Partindo mais uma vez da função original

formula

a descrição das adiciones poderia ser feita com o processo :

   Adicao : PROCESS(s)
      VARIABLE temp : SIGNED(a'RANGE);
   BEGIN temp := (OTHERS =>'0');
      SumaS : FOR i IN s'RANGE LOOP
         temp := temp + SIGNED(s(i));
      END LOOP;
      a <= temp;
   END PROCESS Suma;

Os registros para salvar as amostras seriam outro processo

   Registros : PROCESS(CLOCK)
   BEGIN
      IF rising_edge(CLOCK) THEN
         s <= DI & s(0 TO s'right-1);
      END IF;
   END PROCESS Registros;

Bom, o código necessitaria 512 Flip Flops e 564 LUTs para a mesma arquitetura que antes necessitou 21 Flip-Flops e 78 LUTs, alem do filtro não ser ajustável dinamicamente.

Fica para o leitor investigar porque usando esta versão da descrição a ferramenta não conseguiu otimizar a implementação dos registros mesmo dispondo dos recursos necessários.

Lição ganhada na implementação, se por um lado algum grau de abstração é produtivo não deveríamos descrever hardware sem considerar como a descrição seria implementada numa arquitetura em particular.


Comentários finais


A intenção da nota foi apresentar uma metodologia de trabalho destacando a eficiência no uso dos recursos do que numa solução para um problema, a função para implementar foi simples para não complicar desnecessariamente o código e afastamos da linha de raciocino principal. Mas não por isso as conclusões seriam menos válidas.

Implementar funções DSP em hardware exige do projetista não apenas o conhecimento detalhado da função para implementar mas também o conhecimento da arquitetura de lógica programável sobre a qual seria implementada.

Migrar funções pensadas para outras soluções de implementação tem o risco potencial de criar soluções pouco otimizadas e um consumo de recursos desnecessário. Um ponto que não deveria esquecer quem tenta passar algoritmos de software para hardware.

Uma visão do projeto completo é imprescindível mas não reparar nos detalhes poderia rapidamente acrescentar muitas vezes os recursos consumidos. Se for implementar 32 destes filtros poderíamos gastar desde 21 X 32 = 672 até 533 X 32 = 17056 Flip-Flops dependendo do código e da arquitetura de lógica programável selecionada.


 
   A sua empresa está necessitando uma revisão de projeto ?