FPGA: Confira quantos recursos de hardware são gastos e veja como os otimizar

Recursos de Hardware

A cada dia os avanços tecnológicos colocam à disposição do projetista novos componentes e todo mundo faz de tudo para estar na crista da onda. Os componentes eletrônicos fazem muito mais e são mais rápidos 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 se atentar para o que está fazendo, que é o de 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édia-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 (1)

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

Implementando em hardware

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

Para este 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 selecionadas.

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 fórmula do filtro for implementada em hardware poderia criar muitos níveis de lógica.

Recursos de Hardware: MovingAverage

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.

Recursos de Hardware: Moving Average

Podemos melhorar isso. Analisemos então 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.

Recursos de Hardware: Moving Average

Reduziremos nosso diagrama para apenas duas operações; uma adição e uma subtração. Comparando as duas opções, esta versão tem um diferencial a mais, 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 o valor dos resultados, “A”.

Recursos de Hardware: Moving Average

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

Recursos de Hardware: Moving Average

Mais uma vez, redesenhamos o diagrama de blocos para simplificar. N seria um número relativo ao ajuste do número de passos.

Recursos de Hardware: Moving Average

Descrevendo hardware em VHDL

Vamos descrever em VHDL a arquitetura que terminarmos de criar, iniciaremos 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 descreveremos as estruturas de dados; definimos um tipo de dados ARRAY com comprimento 2N-1 e o número de bits igual ao range dos 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 conforme o código a seguir:

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

   
e o acumulador com as adições e subtrações

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

Finalmente o processo que descreve o Filtro de Média-Móvel (“Moving Average Filter”) 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 do Hardware

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; o sinal do seria o dado retornado pelo filtro. No dado de entrada adicionamos ruído nas amostras para conseguir uma SNR de 25dB, isto para que o sinal de entrada se aproximasse de uma amostragem real. Quantificamos 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, alinhados com o tipo de dado, as formas de onda de entrada, saída e acumulador seriam conforme a apresentados na figura a seguir:

Recursos de Hardware - Simulação

Aplicando um zoom à imagem:

Recursos de Hardware: Simulação

Conseguimos visualizar nas formas de ondas que o filtro estaria funcionando como esperado; os dados de entrada tem mais ruído que os dados de saída. O código completo do filtro ajustável está apreentado a seguir:

——————————————————————————-

—                                                       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, mostrando uma verificação rápida do funcionamento do mux. Veja que o range de saída permanece dentro da mesma faixa e, como esperado, o ruído na saída aumentou ao diminuir o número de passos.

Recursos de Hardware

A Implementação física

Na parte funcional, verificamos então que nosso filtro estaria dando os resultados esperados. Vamos passar para a implementação física. Até este momento o projeto foi genérico quanto aos componentes e s ferramentas; poderia ser implementado utilizando quaisquer componente de lógica programável.

De fato, uma vez ajustado o fluxo de implementação do fornecedor de FPGA à ferramenta, e isto fica fora do escopo deste post, usar um ou outro fornecedor traz pouca diferença no processo. No entanto, não é isso que observamos 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.

Recursos de Hardware em VHDL

A implementação para a Arquitetura 1 resulta em 533 registros. Está excelente, é exatamente este o esperado. Mas alterando o componente, a implementação para uma outra arquitetura de FPGA mostra 21 registros, ou seja, o mesmo código utilizando uma arquitetura diferente dá 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 usadas de 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 multiplexador realmente não consume recursos adicionais, faz parte da mesma LUT usada para salvar as amostras.

Recursos de Hardware: Moving Average

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

Portanto, 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 adições 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 de 512 Flip Flops e 564 LUTs para a mesma arquitetura que antes necessitou apenas 21 Flip-Flops e 78 LUTs, além do filtro não ser ajustável dinamicamente.

Fica então a cargo do 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.

A lição que aprendemos nessa implementação é que, 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 deste post 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.

Esse texto foi originalmente publicado no link e foi revisado por Thiago Lima, que teve autorização do autor para publicação.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Hardware » FPGA: Confira quantos recursos de hardware são gastos e veja como os otimizar
Comentários:
Notificações
Notificar
guest
2 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Willian Henrique
Willian Henrique
18/11/2015 09:15

Muito legal o tutorial.
O programa que você utilizou para simular é free?
Sabe se o modelsim tambem gera gráficos?

Abraços,

Gallegos Walter
Walter Gallegos
Reply to  Willian Henrique
23/11/2015 17:02

Oi William, desculpa o demorado do retorno.

A que usei foi a versão paga do Active-HDL, a WDG é o distribuidor da ferramenta para a região, mas você pode pegar a versão estudante que é free

https://www.aldec.com/en/products/fpga_simulation/active_hdl_student.

Abraços,

Talvez você goste:

Séries

Menu