1 Comentário

Explorando algumas instruções SIMD dos microcontroladores ARM Cortex M4

simd

Olá caro leitor(a), neste pequeno artigo vamos dar uma olhada em uma das características mais citadas dos processadores ARM CM4 (que também faz parte dos novos processadores Cortex M7) mas que vejo alguns colegas com dúvidas ou mesmo com perda de desempenho em algoritmos de processamento de sinais ao programar em qualquer coisa diferente de Assembly, o set de instruções especial SIMD.

Introdução

Vamos antes recapitular o que são os microcontroladores Cortex M4 (ou simplesmente CM4). Segundo a ARM, fabricante desse núcleo, os CM4 são microcontroladores dedicados a tarefas com resposta de tempo real, sendo um superset dos conhecidos Cortex M3 (ou CM3), de modo que o código escrito para este último é totalmente compatível com o superset. Se observamos o diagrama simplificado do processador ARM CM4 veremos que ele é praticamente idêntico ao seu antecessor.

Figura 1 : Núcleos M3 e M4

Porém vejam que temos basicamente dois blocos a mais, a unidade de ponto flutuante (que está fora do escopo deste artigo)  e a CPU, que ambos têm, mas que na figura (b) possui um adicional chamado de extensões DSP. Essas extensões são instruções utilizadas comumente para processamento de sinais e estão incluídas no conjunto de instruções de diversos circuitos integrados específicos para esse fim. Como exemplo podemos citar as instruções de multiplicação-acumulação (MLA) e multiplicação-subtração (MLS), que nos CM4 são executadas em um único ciclo de máquina, bem diferente de um CM3 que, apesar de suportar essas instruções, pode levar até 7 ciclos  de clock como verificado no manual de referência aqui. Além disso uma versão em ponto flutuante do multiplica-acumula também está disponível (VMLA). 

Ok, até aqui chegamos no ponto que o leitor vai pensar: "Essa parte eu já sei, inclusive uso esse processador nos meus projetos graças a essa funcionalidade". Porém o mesmo leitor poderá perguntar-se: "Mas esperava mais desempenho pra esse tipo de aplicação usando um processador que possui essas extesnsões DSP". É pra isso que vamos seguir com esse artigo.

Escrever uma aplicação para processamento de sinais em tempo real seja pra processamento em blocos (comumente mais lento) ou, no pior caso, processamento por amostra, não é uma tarefa das mais triviais, de engenheiros mais experientes. Exige no mínimo uma mistura de código em linguagem de alto nível e baixo nível para implementar o núcleo desses algoritmos e retirar o máximo de overhead possível por execução. No caso de usuários que precisam lidar esporadicamente com DSP em suas aplicações a tarefa pode se tornar uma frustração ao descobrir que a aquele simples filtro digital de apenas 2 pólos leva mais de 100us para executar num processador que diz que possui extensões DSP, mais frustrante ainda é colocar o ARM-GCC para compilar no último nível de otimização para velocidade, -O3, e mesmo assim o tempo cair para 80us.

Introdução às extensões SIMD dos microcontroladores ARM Cortex M4

Mas nem tudo é motivo de tristeza! Ainda incluido no set de instruções especial para DSP dos CM4, existe um grupo especial de instruções presentes em DSPs e processadores high-end chamado de SIMD, Single Instruction, Multiple Data. Ou seja, uma única instrução que opera dados de 2 ou mais fontes. Na prática traduz em executar 2 ou mais operações em um único ciclo de instrução, e os CM4 possuem um conjunto básico, porém interessante, de extensões SIMD. A figura abaixo nos dá uma ideia:

Figura 2 : Instruções microcontroladores ARM Cortex

Vejam que a área em vermelho representa o set de instruções DSP, e reparem que apesar de ofercer operações básicas (esse que vos escreve sente falta de alguma instrução DO ou REPEAT para loops sem overhead), é bem atraente para aplicações que possam exigir processamento digital de sinais no dia-a-dia do engenheiro (aquele filtro digital passa-faixas num equalizador de áudio). Mas agora vem aquela pergunta: "Se tem tudo isso aí de instrução, por que meu belo filtro digital demora tanto pra executar?". Para responder essa pergunta temos que dividir inicialmente o programador em dois grandes grupos, aqueles que utilizam o ambiente de desenvolvimento baseado no ARM-GNU e os que se utilizam de ferramentas proprietárias como KEIL e IAR. Em ambos os casos ao utilizar as opções de build com máxima otimização para velocidade de execução, o compilador irá aumentar a eficiência de operações de fetch de memória, pré-computação de valores, otimizações em estruturas de repetição (otimização essa que aumenta o tamanho do executável) como loop-unrolling, além de operações aritméticas não triviais como seno, cosseno, logaritmos, divisão inteira e radiciação. Mas as ferramentas não reconhecem (ao menos não imediatamente) processamento de sinais como partes que mereçam o uso de instruções SIMD como otimização, resultando em cálculos mais lentos que o esperado.

Mas que instruções são essas? Como já dito, de forma prática executam mais de uma ação em um único ciclo de máquina. Iremos apresentar o uso de algumas delas para que o usuário se familiarize com o processo, porém antes apresentemos as instruções selecionadas para esse artigo: 

  • SADD8  Rd, Rn, Rm - Esta instrução efetua quatro somas simultâneas de 8 bits "empacotados" em Rn com Rm, e guarda o resultado em Rd;
  • SADD16 Rd, Rn, Rm - Essa aqui efetua duas somas de 16 bits cujos os valores são "empacotados" em Rn, Rm com resultado salvo em Rd;
  • SSUB8 Rd, Rn, Rm - Dispensa comentários, efetua o processo inverso de SADD8, ou seja, quatro subtrações de 8 bits nas mesmas condições;
  • SSUB16 Rd, Rn, Rm -  Como SADD16, porém efetua duas subtrações de 16 bits;
  • SMLAD Rd, Rn, Rm, Ra - Considero uma das mais legais, considerando dois valores de 16 bits "empacotados" em Rn, e também em Rm, ambos multiplicam entre si, seus respectivos half-words, ou seja, parte alta com parte alta e baixa com baixa. A seguir somam-se os resultados, que são acumulados em Ra e guardados em Rd. Basicamente, executa um multiplica e acumula duas vezes no mesmo ciclo!

Agora creio que o leitor ao ler uma versão mais "abrasileirada" do descritivo dessas instruções, tenha tido diversas ideias de como por aquele seu CM4 para processar sinais digitalmente. Na lista completa vais encontrar diferentes variações dessas instruções, inclusive extensões que automaticamente saturam o resultado no mesmo ciclo.

Mas vejam que essas mesmas instruções aumentam o número de operações no mesmo ciclo a medida que o tamanho de dados manipulado descresce, caso de SADD8/16 em que a operação com 16 bits são duas contra 4 em 8 bits, entretanto vejam que grande parte das aplicações em processamento de sinais podem ser plenamente satisfeitas utilizando resolução de 8 e 16 bits. Em 8 bits, por exemplo, já oferece um ótimo desempenho em uma aplicação de rádio definido por software (um demodulador QPSK por exemplo), já para 16 bits, podemos trabalhar com boa qualidade em aplicações de processamento de áudio, inclusive englobando amplificação em classe D utilizando técnicas como sigma-delta. 

Da mesma forma, a instrução SMLAD executa duas MACs (Multiplica e acumula) entre 4 valores de 16 bits, que são colocados em dois registradores, entregando um resultado de 32 bits, ou seja, um filtro digital FIR com 4 estágios pode teoricamente ser executado em 2 ciclos de máquinas, ante a 4 utilizando as instruções básicas de DSP como MLA ou MLS.

Utilizando as instruções SIMD sem ajuda do compilador

Agora chega de teoria, e vamos pra prática, como utilizar essas belas instruções mesmo que a toolchain se negue a utilizar. A forma mais óbvia é admitir que o melhor compilador não susbtitui o melhor programador, ou seja, escreva em assembly e importe a chamada da função em C. Primeiro vamos de SADD8, implementando uma soma de vetores.

O arquivo "C" contém a estrutura de um vetor de três componentes, além do protótipo da função: 

E no arquivo .S a implementação em Assembly : 

Interessante não? Vale lembrar que esse processo é válido pra qualquer ARM que possua set de instrução SIMD. Vejam que o intuito é demonstrar a capacidade das extensoes, por isso não estou me preocupando em checar os argumentos ou mesmo overflow dos operandos. Se fossemos deixar isso a cargo do compilador talvez além do overhead para salvamento e restauração dos registradores usados, o código envolveria o ciclo de load/store 3 vezes, mais três somas, resultando em não menos que 11 ciclos para realizar a operação. Devemos ainda ter em mente que instruções SIMD utilizam os operandos "empacotados" nos registradores, ou seja, no primeiro operando temos 4 bytes que formam um "lado" da soma, o valor que será somado, e virão os outros 4 bytes do segundo operando.

Vejamos uma aplicação mais usada, calculando a média de um vetor de dados, para uso estatístico. Da mesma forma, o arquivo .h: 

E sua implementação: 

Um pouquinho mais de trabalho, porém ficou bem mais eficiente. A única limitação é que o tamanho do vetor de dados deve ser um múltiplo inteiro de 4, porém com algumas checagens é possível receber vetores de qualquer tamanho. Percebam ainda que graças ao uso de uma instrução SIMD, fizemos de forma implícita o loop unrolling uma vez que 4 valores são somados por iteração, com isso reduzindo o overhead do loop.

Mais um exemplo, só que específico? Que tal um filtro FIR de 4 estágios? 

E no assembly: 

Uma beleza não? Reparem que existe aqui uma otimização focada na arquitetura do ARM, primeiro todos os acessos de memória são executados, isso porque um ciclo de load ou store leva 2 ciclos de máquina se executado, porém quando esse é executado em sequência, apenas o primeiro fetch levam 2 ciclos, os subsequentes passam a levar apenas 1 ciclo, com isso além de executar o núcleo do filtro em 2 instruções, ainda ganhamos 3 ciclos pelos acessos de memória agrupados. Dica: em C o ARM-GNU e todas as outras toolchains entendem fetches seguidos atribuindo todas as variáveis em sequência antes mesmo de qualquer outra operação.

Mas e em C? Como que eu faço? 

Ah, mas o leitor nem sempre vai querer misturar código de baixo nível na sua aplicação, então você me pergunta: "Programo apenas em C ou C++, como farei para ter acesso a esses benefícios?" Calma, a turma da ARM pensou nisso e junto com os arquivos de suporte ao processador oferecidos no pacote CMSIS existe um wrapper chamado de core_cm4_simd.h, em que as instruções SIMD são declaradas como simples macros e são substituídas pela instrução em assembly, após o build.  Um exemplo legal, estou desenvolvendo um equalizador gráfico digital para o ARM design contest, e estou testando um filtro FIR de 19 estágios, vejam como ficou um pedaço do código usando o wrapper fornecido na CMSIS: 

Vejam dentro do loop principal que para executar o filtro parti para duas estratégias, a primeira é fazer loop unrolling por fator 9, para filtrar as 18 primeiras amostras de duas em duas usando a instrução de MAC dupla usando a macro __SMLAD(), e obtive um excelente resultado. Junto com esse, rodam mais 15 filtros iguais sincronizados processando blocos de 512 amostras, além do ganho de desempenho, e eficiência, o código ficou bastante reusável, tendo que me preocupar em fazer o ajuste dos coeficientes do filtro.

Conclusão? Espero que esse artigo tenha removido mesmo que parcialmente a frustração do leitor de que o ARM parece um DSP mas não é, realmente ele continua não sendo, mas creio que com a elucidação de alguns conceitos de suas instruções podemos todos concordar que ele sim pode realizar tarefas de processamento de sinais aos quais a ARM diz que ele se adequa.

Talvez um futuro próximo, publiquemos algum projetinho de bancada útil para se aproveitar desse set extendido DSP desse pequeno notável. Então é isso, até a próxima.

Referências

http://www.arm.com/products/processors/cortex-m/index.php
http://infocenter.arm.com/help/index.jsp
https://launchpad.net/gcc-arm-embedded
https://answers.launchpad.net/gcc-arm-embedded/+question/196873
http://www.keil.com/arm/mdk.asp
https://www.iar.com/iar-embedded-workbench/arm/
http://en.wikipedia.org/wiki/Loop_unrolling

YIU, Joseph - The definitive guide to ARM Cortex M3 and ARM Cortex M4 Processors - 2014

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.

Software » Explorando algumas instruções SIMD dos microcontroladores ARM Cortex M4
Talvez você goste:
Comentários:

1
Deixe um comentário

avatar
1 Comentários
0 Respostas
0 Seguidores
 
Discussão de maior alcance
Discussão mais quente
1 Autores de comentários
Ademario Carvalho Comentários recentes
  Notificações  
recentes antigos mais votados
Notificar
Ademario Carvalho
Visitante
Ademario

Excelente artigo Felipe!!! Também sou adepto da filosofia "conhecimento para todos". Parabéns!!!

Séries

Menu