Em um aplicativo em tempo real¹ em um ARM Cortex M3 (semelhante ao STM32F101), preciso pesquisar um pouco do registro de um periférico interno até que seja zero, o mais estreito possível. Eu uso a banda de bits para acessar o bit apropriado. O código C (de trabalho) é
while (*(volatile uint32_t*)kMyBit != 0);
Esse código é copiado na RAM executável no chip. Após alguma otimização manual², o ciclo de polling é reduzido para o seguinte, que eu cronometrei³ a 6 ciclos:
0x00600200 681A LDR r2,[r3,#0x00]
0x00600202 2A00 CMP r2,#0x00
0x00600204 D1FC BNE 0x00600200
Como a incerteza da votação pode ser reduzida? Um loop de 5 ciclos seria adequado ao meu objetivo: amostrar o mesmo bit o mais próximo possível de 15,5 ciclos depois de zero.
Minhas especificações exigem a detecção confiável de um pulso baixo, pelo menos, 6,5 ciclos de clock da CPU; classificá-lo de forma confiável como curta se durar menos de 12,5 ciclos; e classificá-lo de forma confiável desde que dure mais de 18,5 ciclos. Os pulsos não têm relação de fase definida com o relógio da CPU, que é minha única referência de tempo precisa. Isso requer um loop de votação de no máximo 5 horas. Na verdade, estou emulando código que rodava em uma CPU de 8 bits de décadas que poderia pesquisar com um ciclo de 5 horas, e o que isso fez se tornou a especificação.
Tentei compensar o alinhamento do código inserindo NOP antes do loop, nas muitas variantes que tentei, mas nunca observei uma alteração.
Tentei inverter o CMP e o LDR, mas ainda recebo 6 ciclos:
0x00600200 681A LDR r2,[r3,#0x00]
; we loop here
0x00600202 2A00 CMP r2,#0x00
0x00600204 681A LDR r2,[r3,#0x00]
0x00600206 D1FC BNE 0x00600202
Este é 8 ciclos
0x00600200 681A LDR r2,[r3,#0x00]
0x00600202 681A LDR r2,[r3,#0x00]
0x00600204 2A00 CMP r2,#0x00
0x00600206 D1FB BNE 0x00600200
Mas este é 9 ciclos:
0x00600200 681A LDR r2,[r3,#0x00]
0x00600202 2A00 CMP r2,#0x00
0x00600204 681A LDR r2,[r3,#0x00]
0x00600206 D1FB BNE 0x00600200
¹ Medir quanto tempo o bit está baixo, em um contexto em que nenhuma interrupção ocorre.
² O código inicial gerado pelo compilador usou r12 como o registro de destino e adicionou 4 bytes de código ao loop, custando 1 ciclo.
³ Os números fornecidos são obtidos com um emulador STIce em tempo real com precisão de ciclo supostamente preciso e seu recurso de acionador de emulador quando lido no endereço do registro. Anteriormente, tentei o contador "States" com um ponto de interrupção no loop, mas o resultado depende da localização do ponto de interrupção. A etapa única é ainda pior: sempre dá 4 ciclos para o LDR, quando isso é pelo menos em algum momento até 3.
fonte
gcc -Os -mcpu=cortex-m3
?ldr
/cbz reg, end_of_loop
para os internos e ainda assimcmp
/bnz
na parte inferior. Mas isso daria a você um intervalo de pesquisa não uniforme, por exemplo, 1 em cada 8 pesquisas, caso isso importe.Respostas:
Se eu entendi a pergunta corretamente, não são necessariamente os ciclos de loop que precisam ser reduzidos, mas o número de ciclos entre as amostras resultantes (por exemplo, instruções LDR). Mas pode haver mais de um LDR por iteração. Você pode tentar algo como isto:
O espaçamento entre as duas instruções LDRB varia para que as amostras não sejam uniformemente espaçadas.
Isso pode atrasar levemente a saída do loop, mas da descrição do problema não posso dizer se é importante ou não.
Por acaso, tenho acesso ao modelo M7 com precisão de ciclo e quando o processo estabiliza seu loop original é executado no M7 em 3 ciclos por iteração (ou seja, LDR a cada 3 ciclos), enquanto o loop proposto acima é executado em 4 ciclos, mas agora existem dois LDRs lá (então LDR a cada 2 ciclos). A taxa de amostragem é definitivamente melhorada.
Para dar crédito, o desenrolar do CBZ como uma pausa foi proposto por @Peter Cordes em um comentário .
É certo que o M3 será mais lento, mas ainda vale a pena tentar, se for a taxa de amostragem que você procura.
Além disso, você pode verificar se o LDRB em vez do LDR (como no código acima) muda alguma coisa, embora eu não espere.
UPD: Eu tenho outra versão de loop 2-LDR que no M7 é concluída em 3 ciclos, os quais você pode tentar por interesse (também as quebras de CBZ permitem o fácil equilíbrio dos caminhos após o loop):
fonte
r2
o que vair1
, mas 6 ciclos do que vair1
para o que vair2
(devido aob loop
intervalo), quando eu quero (no máximo) 5 ciclos entre amostragens. Um problema facilmente resolvido é que,out
após um atraso maior após a amostra , alcançamos se a saída é porquer1
é zero do que quando é porquer2
é zero. Em outras notícias,ldrb
de um endereço de banda de bits causou problemas, alterado paraldr
.nop
antesloop:
e 4nop
depoisout_fast:
fazem com que aldr
apósout_slow:
amostras 10 ciclos após a amostra que foi vista pela primeira vez em zero, o que for um dos três. Minhas especificações (conforme formuladas na pergunta) exigem 13, e isso é trivial para ajustar. Problema 100% resolvido! Muito obrigado, assim como Peter Cordes por seu comentário e B Degan pela primeira recompensa.out_fast
talvez seja mais compacto que 5nop
s, talvez apenas faça outroldr
ou faça umab
para a próxima instrução, se isso levar mais ciclos do que um NOP em sua CPU sem poluir a previsão de ramificação (se houver).Você pode tentar isso, mas desconfio que ele dará os mesmos 6 ciclos
fonte