Olá pessoal! Este artigo se concentrará em uma parte importante do processamento de sinal digital - a filtragem do sinal da janela, em particular nos FPGAs. O artigo mostrará como projetar janelas clássicas de comprimento padrão e janelas "longas" de amostras de 64K a 16M +. A principal linguagem de desenvolvimento é VHDL, a base do elemento são os cristais Xilinx FPGA mais recentes das famílias mais recentes: são Ultrascale, Ultrascale +, 7-series. O artigo demonstrará a implementação do CORDIC - o kernel básico para configurar funções de janela de qualquer duração, bem como funções básicas de janela. O artigo descreve o método de design usando linguagens de alto nível C / C ++ no Vivado HLS. Como de costume, no final do artigo, você encontrará um link para os códigos-fonte do projeto.
KDPV: um esquema típico de transmissão de sinal através de nós DSP para tarefas de análise de espectro.

1. Introdução
No curso “Processamento digital de sinais”, muitas pessoas sabem que, para uma forma de onda senoidal infinita no tempo, seu espectro é uma função delta na frequência do sinal. Na prática, o espectro de um sinal harmônico limitado em tempo real é equivalente à função
~ sin (x) / x , e a largura do lobo principal depende da duração do intervalo de análise de sinal
T. O limite de tempo nada mais é do que multiplicar o sinal por um envelope retangular. Sabe-se pelo curso DSP que a multiplicação de sinais no domínio do tempo é uma convolução de seus espectros no domínio da frequência (e vice-versa); portanto, o espectro do envelope retangular limitado do sinal harmônico é equivalente a ~ sinc (x). Isso também se deve ao fato de não podermos integrar o sinal por um intervalo de tempo infinito, e a transformada de Fourier, de forma discreta, expressa por uma soma finita, é limitada pelo número de amostras. Como regra, o comprimento da FFT nos modernos dispositivos de processamento digital FPGA leva os valores
NFFT de 8 a vários milhões de pontos. Em outras palavras, o espectro do sinal de entrada é calculado no intervalo
T , que em muitos casos é igual a
NFFT . Limitando o sinal ao intervalo
T , impomos assim uma “janela” retangular com uma duração de amostras
T. Portanto, o espectro resultante é o espectro do sinal harmônico multiplicado e o envelope retangular. Nas tarefas DSP, janelas de várias formas são inventadas há muito tempo, as quais, quando sobrepostas a um sinal no domínio do tempo, podem melhorar suas características espectrais. Um grande número de várias janelas se deve principalmente a um dos principais recursos de qualquer sobreposição de janela. Esse recurso é expresso na relação entre o nível dos lobos laterais e a largura do lobo central. Um padrão bem conhecido: quanto mais forte a supressão dos lobos laterais, maior o lobo principal e vice-versa.
Uma das aplicações das funções da janela: detecção de sinais fracos contra o fundo dos mais fortes, suprimindo o nível dos lobos laterais. As funções da janela principal nas tarefas DSP são uma janela triangular, sinusoidal, Lanczos, Hann, Hamming, Blackman, Harris, Blackman-Harris, janela flat-top, Natall, Gauss, Kaiser e muitas outras. A maioria deles é expressa através de uma série finita pela soma de sinais harmônicos com pesos específicos. As janelas de formato complexo são calculadas usando um expoente (janela Gaussiana) ou uma função Bessel modificada (janela Kaiser) e não serão consideradas neste artigo. Você pode ler mais sobre funções de janela na literatura, que tradicionalmente darei no final do artigo.
A figura a seguir mostra as funções típicas da janela e suas características espectrais criadas usando as ferramentas CAD do Matlab.

Implementação
No início do artigo, inseri o KDPV, que mostra em termos gerais um diagrama estrutural da multiplicação dos dados de entrada por uma função da janela. Obviamente, a maneira mais fácil de implementar o armazenamento de uma função de janela no FPGA é gravá-la na memória (bloco
RAMB ou Distributed
Distributed - isso não importa muito) e, em seguida, recuperar dados ciclicamente no momento das amostras do sinal de entrada. Como regra, nos modernos FPGAs, a quantidade de memória interna permite armazenar funções de janela de tamanhos relativamente pequenos, que são então multiplicados pelos sinais de entrada recebidos. Por pequena, quero dizer que a janela funciona com até 64K amostras de comprimento.
Mas e se a função da janela for muito longa? Por exemplo, 1 milhão de leituras. É fácil calcular que, para essa função de janela apresentada em uma grade de 32 bits, são necessárias células de memória de bloco NRAMB = 1024 * 1024 * 32/32768 = 1024 dos cristais FPGA Xilinx do tipo RAMB36K. E para amostras de 16 milhões? 16 mil células de memória! Nem um único FPGA moderno tem tanta memória. Para muitos FPGAs, isso é demais e, em outros casos, é um uso desnecessário dos recursos do FPGA (e, é claro, do dinheiro do cliente).
Nesse sentido, você precisa criar um método para gerar amostras de função de janela diretamente para o FPGA em tempo real, sem gravar coeficientes do dispositivo remoto na memória do bloco. Felizmente, as coisas básicas já foram inventadas para nós. Usando um algoritmo como o
CORDIC (o método
dígito por dígito ), é possível projetar muitas funções de janela cujas fórmulas são expressas em termos de sinais harmônicos (Blackman-Harris, Hann, Hamming, Nattal, etc.)
CORDIC
O CORDIC é um método iterativo simples e conveniente para calcular a rotação de um sistema de coordenadas, que permite calcular funções complexas executando operações primitivas de adição e deslocamento. Usando o algoritmo CORDIC, é possível calcular os valores dos sinais harmônicos sin (x), cos (x), encontrar as fases - atan (x) e atan2 (x, y), funções trigonométricas hiperbólicas, girar o vetor, extrair a raiz do número, etc.
No começo, eu queria pegar o kernel CORDIC finalizado e reduzir a quantidade de trabalho, mas tenho uma longa antipatia pelos kernels Xilinx. Depois de estudar os repositórios no github, percebi que todos os kernels apresentados não são adequados por várias razões (mal documentados e ilegíveis, não universais, criados para uma tarefa ou base de elementos específica,
escritas em verilog etc.). Então pedi ao camarada
lazifo que fizesse esse trabalho para mim. Obviamente, ele lidou com isso, porque a implementação do CORDIC é uma das tarefas mais simples no campo do DSP. Mas como sou impaciente, paralelamente ao trabalho dele, escrevi
minha bicicleta com meu
próprio núcleo parametrizado. Os principais recursos são a profundidade de bits configurável dos sinais de saída
DATA_WIDTH e a fase de entrada normalizada
PHASE_WIDTH de -1 a 1, e a precisão dos cálculos de
PRECISION . O núcleo CORDIC é executado de acordo com o circuito paralelo da tubulação - a cada ciclo do relógio, o núcleo está pronto para realizar cálculos e receber amostras de entrada. O kernel gasta N ciclos para calcular a amostra de saída, cujo número depende da capacidade das amostras de saída (quanto mais capacidade, mais iterações para calcular o valor de saída). Todos os cálculos ocorrem em paralelo. Assim, CORDIC é o núcleo básico para a criação de funções da janela.
Funções da janela
Na estrutura deste artigo, percebo apenas as funções da janela que são expressas através de sinais harmônicos (Hann, Hamming, Blackman-Harris de várias ordens, etc.). O que é necessário para isso? Em termos gerais, a fórmula para construir uma janela se parece com uma série de comprimento finito.

Um certo conjunto de coeficientes
ak e membros da série determina o nome da janela. A mais popular e frequentemente usada é a janela Blackman-Harris: de ordem diferente (de 3 a 11). A seguir, é apresentada uma tabela de coeficientes para janelas Blackman-Harris:

Em princípio, o conjunto de janelas Blackman-Harris é aplicável em muitos problemas de análise espectral e não é necessário tentar usar janelas complexas, como Gauss ou Kaiser. As janelas Nattal ou flat-top são apenas um tipo de janela com pesos diferentes, mas os mesmos princípios básicos que a Blackman-Harris. Sabe-se que quanto mais membros da série, mais forte é a supressão do nível dos lobos laterais (sujeita a uma escolha razoável da profundidade de bits da função da janela). Com base na tarefa, o desenvolvedor apenas precisa escolher o tipo de janela usada.
Implementação de FPGA - abordagem tradicional
Todos os núcleos de funções de janela são projetados usando a abordagem clássica para descrever circuitos digitais em FPGAs e são escritos na linguagem VHDL. Abaixo está uma lista dos componentes feitos:
- bh_win_7term - Blackman-Harris 7, uma janela com supressão máxima de andaimes laterais.
- bh_win_5term - pedido da Blackman-Harris 5, inclui uma janela com uma parte superior plana.
- bh_win_4term - Blackman-Harris 4 pedidos, inclui a janela Nattal e Blackman-Nattal.
- bh_win_3term - Blackman-Harris 3 pedidos,
- hamming_win - janelas de Hamming e Hann.
O código fonte do componente de janela Blackman-Harris é de 3 ordens de grandeza:
entity bh_win_3term is generic ( TD : time:=0.5ns;
Em alguns casos, usei a biblioteca
UNISIM para incorporar os
nós DSP48E1 e DSP48E2 no projeto, o que finalmente
me permite aumentar a velocidade dos cálculos devido à tubulação dentro desses blocos, mas, como a prática demonstrou, é mais rápido e fácil dar rédea livre e escrever algo como
P = A * B + C e especifique as seguintes diretivas no código:
attribute USE_DSP of <signal_name>: signal is "YES";
Isso funciona bem e define rigidamente o tipo de elemento no qual a função matemática é implementada para o sintetizador.
Vivado hls
Além disso, implementei todos os núcleos usando as ferramentas
Vivado HLS . Vou listar as principais
vantagens do Vivado HLS: alta velocidade de design (
time-to-market ) em linguagens de alto nível C ou C ++, modelagem rápida de nós desenvolvidos devido à falta de um conceito de frequência de relógio, configuração flexível de soluções (em termos de recursos e desempenho), introduzindo pragmas e diretrizes do projeto, bem como um baixo limite de entrada para desenvolvedores em idiomas de alto nível. A principal desvantagem é o custo subótimo dos recursos FPGA em comparação com a abordagem clássica. Além disso, não é possível atingir as velocidades fornecidas pelos métodos antigos clássicos de RTL (VHDL, Verilog, SV). Bem, a maior
desvantagem é dançar com um pandeiro, mas isso é característico de todo CAD da Xilinx. (Nota: no depurador Vivado HLS e no modelo C ++ real, muitas vezes foram obtidos resultados diferentes, porque o Vivado HLS trabalha de maneira torta usando as vantagens da
precisão arbitrária ).
A imagem a seguir mostra o log do kernel CORDIC sintetizado no Vivado HLS. É bastante informativo e exibe muitas informações úteis: a quantidade de recursos utilizados, a interface do usuário do kernel, os loops e suas propriedades, o atraso na computação, o intervalo para o cálculo do valor de saída (importante ao projetar circuitos seriais e paralelos):

Você também pode ver a maneira de calcular dados em vários componentes (funções). Pode-se observar que na fase zero, os dados da fase são lidos e nas etapas 7 e 8, o resultado do nó CORDIC é exibido.

O resultado do Vivado HLS: um kernel RTL sintetizado criado a partir do código C. O log mostra que na análise de tempo, o kernel passa com êxito todas as restrições:

Outra grande vantagem do Vivado HLS é que, para verificar o resultado, ela mesma faz um banco de testes do código RTL sintetizado com base no modelo usado para verificar o código C. Este pode ser um teste primitivo, mas acredito que seja muito interessante e conveniente o suficiente para comparar a operação do algoritmo em C e em HDL. Abaixo está uma captura de tela do Vivado mostrando uma simulação do modelo de função do kernel de uma função de janela obtida pelo Vivado HLS:

Assim, para todas as funções da janela, resultados semelhantes foram obtidos, independentemente do método de design - em VHDL ou em C ++. No entanto, no primeiro caso, é obtida uma maior frequência de operação e um número menor de recursos, e no segundo caso, a velocidade máxima do projeto é alcançada. Ambas as abordagens têm direito à vida.
Calculei especificamente quanto tempo gastaria no desenvolvimento usando métodos diferentes. Eu implementei um projeto C ++ no Vivado HLS ~ 12 vezes mais rápido que no VHDL.
Comparação de abordagens
Compare o código-fonte para HDL e C ++ para o núcleo CORDIC. O algoritmo, como foi dito anteriormente, é baseado nas operações de adição, subtração e deslocamento. No VHDL, fica assim: existem três vetores de dados - um é responsável pela rotação do ângulo e os outros dois determinam o comprimento do vetor ao longo dos eixos X e Y, o que é equivalente a sin e cos (veja a figura no wiki):

Ao calcular iterativamente o valor Z, os valores X e Y são calculados em paralelo. O processo de pesquisa cíclica dos valores de saída no HDL:
constant ROM_LUT : rom_array := ( x"400000000000", x"25C80A3B3BE6", x"13F670B6BDC7", x"0A2223A83BBB", x"05161A861CB1", x"028BAFC2B209", x"0145EC3CB850", x"00A2F8AA23A9", x"00517CA68DA2", x"0028BE5D7661", x"00145F300123", x"000A2F982950", x"000517CC19C0", x"00028BE60D83", x"000145F306D6", x"0000A2F9836D", x"0000517CC1B7", x"000028BE60DC", x"0000145F306E", x"00000A2F9837", x"00000517CC1B", x"0000028BE60E", x"00000145F307", x"000000A2F983", x"000000517CC2", x"00000028BE61", x"000000145F30", x"0000000A2F98", x"0000000517CC", x"000000028BE6", x"0000000145F3", x"00000000A2FA", x"00000000517D", x"0000000028BE", x"00000000145F", x"000000000A30", x"000000000518", x"00000000028C", x"000000000146", x"0000000000A3", x"000000000051", x"000000000029", x"000000000014", x"00000000000A", x"000000000005", x"000000000003", x"000000000001", x"000000000000" ); pr_crd: process(clk, reset) begin if (reset = '1') then
No C ++, no Vivado HLS, o código parece quase o mesmo, mas o registro é várias vezes menor:
Aparentemente, o mesmo ciclo com turnos e acréscimos é usado. No entanto, por padrão, todos os loops no Vivado HLS são "recolhidos" e executados sequencialmente, conforme planejado para a linguagem C ++. A introdução do
pragma HLS UNROLL ou
HLS PIPELINE converte serial em cálculos paralelos. Isso leva a um aumento nos recursos do FPGA, no entanto, permite calcular e enviar novos valores ao núcleo a cada ciclo do relógio.
Os resultados da síntese do projeto em VHDL e C ++ são apresentados na figura abaixo. Como você pode ver, logicamente, a diferença é duas vezes a favor da abordagem tradicional. Para outros recursos do FPGA, a discrepância é insignificante. Não me aprofundou na otimização do projeto em C ++, mas, sem ambiguidade, ao definir várias diretivas ou alterar parcialmente o código, o número de recursos utilizados pode ser reduzido. Em ambos os casos, os tempos convergiram para uma dada frequência principal de ~ 350 MHz.

Recursos de implementação
Como os cálculos são realizados em um formato de ponto fixo, as funções da janela possuem vários recursos que devem ser levados em consideração ao projetar sistemas DSP em FPGAs. Por exemplo, quanto maior a profundidade de bits dos dados da função da janela, melhor a precisão da sobreposição da janela. Por outro lado, com profundidade de bits insuficiente da função da janela, distorções serão introduzidas na forma de onda resultante, o que afetará a qualidade das características espectrais. Por exemplo, uma função de janela deve ter pelo menos 20 bits quando multiplicada por um sinal com duração de 2 ^ 20 = 1M de amostras.
Conclusão
Este artigo mostra uma maneira de criar funções de janela sem usar memória externa ou memória de bloco FPGA. O método de usar recursos exclusivamente lógicos de FPGAs (e em alguns casos blocos DSP) é fornecido. Utilizando o algoritmo CORDIC, é possível obter funções de janela com qualquer profundidade de bits (dentro da razão), de qualquer comprimento e ordem, e, portanto, ter um conjunto de praticamente todas as características espectrais da janela.
Como parte de um dos estudos, consegui obter um núcleo estável da função de janela Blackman-Harris de 5 e 7 ordens de magnitude em amostras de 1M a uma frequência de ~ 375 MHz e também para criar um gerador de coeficientes rotativos para uma FFT baseada em CORDIC a uma frequência de ~ 400 MHz. Cristal FPGA usado: Kintex Ultrascale + (xcku11p-ffva1156-2-e).
Link para
o projeto github aqui . O projeto contém um modelo matemático no Matlab, códigos-fonte para funções de janela e CORDIC em VHDL, bem como modelos das funções de janela listadas em C ++ para Vivado HLS.
Artigos úteis
Também aconselho um livro muito popular sobre DSP -
Ayficher E., Jervis B. Processamento de sinais digitais. Abordagem práticaObrigado pela atenção!