Loop de sondagem mais rápido - como posso cortar 1 ciclo da CPU?

8

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.

fgrieu
fonte
o alinhamento pode importar, o domínio do relógio gpio pode dominar o desempenho e os estados de espera do flash; serão mais de 3 relógios, mas podem ser mais de 6 ou mais, dependendo. Eu esperaria que a banda de bits não fosse um sucesso de desempenho para leituras, mas você poderia testar o bit em vez de comparar para zero e ver. linha de fundo você tem que apenas experimentá-lo ...
old_timer 17/01
1
O modo Thumb não tem uma instrução cbnz para comparar e ramificar em outro registro que seja zero? Você compilou com gcc -Os -mcpu=cortex-m3?
Peter Cordes
1
@ Peter Cordes: Não estou usando o gcc, mas o ArmCC 5 (o compilador da geração anterior do ARM antes de ir para o LLVM). A otimização é por tempo e ao máximo, e as opções para a CPU devem ser definidas automaticamente pelo IDE, mas vou verificar isso. Sim, existe o CBZ / CBNZ, mas enquanto eu leio o documento, ele não pode se ramificar para trás.
fgrieu 17/01
1
Ok, então você (ou o compilador) pode desenrolar-se com ldr/ cbz reg, end_of_looppara os internos e ainda assim cmp/ bnzna parte inferior. Mas isso daria a você um intervalo de pesquisa não uniforme, por exemplo, 1 em cada 8 pesquisas, caso isso importe.
Peter Cordes
2
Tem certeza de que não interpretou mal as especificações? Talvez a especificação se refira a "ciclos específicos do dispositivo que não são da CPU" (por exemplo, um timer ou UART com sua própria fonte de clock que possui ciclos muito mais lentos) e talvez "tão curto quanto 13 ciclos específicos do dispositivo" possam ser "tão curtos como 13000 ciclos específicos da CPU ".
Brendan

Respostas:

8

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:

    ldrb    r1, [r0]

loop:
    cbz     r1, out
    ldrb    r2, [r0]
    cbz     r2, out
    ldrb    r1, [r0]
    b       loop

out:

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):

    ldr     r1, [r0]

loop:
    ldr     r2, [r0]
    cbz     r1, out_slow
    cbz     r2, out_fast
    ldr     r1, [r0]
    b       loop

out_fast:
    /* NOPs as required */

out_slow:
alex_mv
fonte
Boas notícias: isso é executado em 10 ciclos / loop com 2 amostragens, portanto, a taxa média de amostragem é boa. Problema sério: o atraso entre as amostras é de 4 ciclos do que vai para r2o que vai r1, mas 6 ciclos do que vai r1para o que vai r2(devido ao b loopintervalo), quando eu quero (no máximo) 5 ciclos entre amostragens. Um problema facilmente resolvido é que, outapós um atraso maior após a amostra , alcançamos se a saída é porque r1é zero do que quando é porque r2é zero. Em outras notícias, ldrbde um endereço de banda de bits causou problemas, alterado para ldr.
fgrieu 23/01
2
Que bom que funcionou :) Não queria equalizar as amostras antes de confirmar que a ideia básica funcionava para você. Também é difícil prever como minhas execuções no M7 se traduzem no M3. Eu lhe dei essa versão de loop porque produzia uma taxa de amostragem uniforme no M7 (LDR a cada 2 ciclos). Mas eu também tinha outra versão que realmente foi mais rápida no M7 (3 ciclos por loop e ainda 2 LDRs), você pode tentar por interesse. Vou atualizar minha resposta.
alex_mv 24/01
3
Confirmo que o seu segundo ciclo de pesquisa é executado em 10 ciclos com duas amostras igualmente espaçadas no meu emu Cortex-M3. Ele responde minha resposta (agora removida) à simplicidade e permite testar pulsos mais curtos. 2 nopantes loop:e 4 nopdepois out_fast:fazem com que a ldrapós out_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.
fgrieu 26/01
@ fgrieu: oh sim, esse é um truque inteligente na atualização mais recente. out_fasttalvez seja mais compacto que 5 nops, talvez apenas faça outro ldrou faça uma bpara 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).
Peter Cordes
1

Você pode tentar isso, mas desconfio que ele dará os mesmos 6 ciclos

0x00600200 581a      LDR      r2,[r3,r0]; initialize r0 to 0x0
0x00600202 4282      CMP      r2,r0
0x00600204 D1FC      BNE      0x00600200
RAVI KANT VERMA
fonte
4
Eu tentei: 6 ciclos :-(
fgrieu 23/01