Ensamblaje AVR: la forma más rápida de incrementar dos bytes combinados

¿Cuál podría ser la forma más rápida de incrementar dos bytes combinados en ensamblador (suponiendo que estoy trabajando en una CPU de 8 bits)? Actualmente estoy haciendo esto:

OVF1_handler: ; TIMER1 overflow ISR

lds r21, timerhl ; load low byte into working register; 2 cycles
add r21, counter_inc ; add 1 to working register (value of counter_inc is 1); 1 cycle

brbs 0, OVF1_handler_carry ; branch if bit 0 (carry flag bit) of SREG is set; 1 cycle if false . 2 cycles if true
sts timerhl, r21 ; otherwise write value back to variable; 2 cycles
reti ; we're done

OVF1_handler_carry: ; in case of carry bit is set
    sts timerhl, r21 ; write value of low byte back to variable; 2 cycles

    lds r21, timerhh ; load high byte into working register; 2 cycles
    inc r21 ; increment it by 1 (no carry check needed here); 1 cycle
    sts timerhh, r21 ; write value of high byte back to variable; 2 cycles

reti ; we're done

Así que en suma hay

255 * (2+1+1+2) + (2+1+2+2+2+1+2) = 1542 cycles

para contar de 0 a 256 (255 veces (2+1+1+2) porque no hay desbordamiento más 1 vez (2+1+2+2+2+1+2) cuando ocurre desbordamiento).

¿Mi cálculo es correcto y hay una forma más rápida?

Respuestas (1)

Confía un poco más en tu compilador. Escribe el código en C, compilalo y mira el desmontaje. No estoy seguro de qué cadena de herramientas usa, pero avr-gcc crea un código bastante bien optimizado.

lds     r24 , lowbyte   ; 2 clocks
lds     r25 , highbyte  ; 2 clocks
adiw    r24 , 0x01      ; 2 clocks - Add Immediate to Word (= 16 bit)
sts     lowbyte  , r24  ; 2 clocks
sts     highbyte , r25  ; 2 clocks

Puede desensamblar el archivo .elf con el siguiente comando (siempre que use la cadena de herramientas gcc):

avr-objdump -C -d $(src).elf

Por cierto: probablemente necesite empujar los registros usados ​​para apilarlos de antemano y abrirlos después (2 ciclos cada uno). Recuerde también que una interrupción (incluido reti) dura al menos 8 ciclos de reloj aparte de las instrucciones que se ejecutan.

; TIMER1_OVF            ;  4 clocks
push    r24             ;  2 clocks
IN      r24 , SREG      ;  1 clock  - save CPU flags
push    r24             ;  2 clocks
push    r25             ;  2 clocks
; do the addition above - 10 clocks
pop     r25             ;  2 clocks
pop     r24             ;  2 clocks
OUT     SREG , r24      ;  1 clock
pop     r24             ;  2 clocks
reti                    ;  4 clocks
; total 32 clock ticks
O puede dar avr-gccun argumento para desmontar la salida en el proceso de compilación.
Personalmente, creo que avr-gcc está generando una lista desordenada, aunque contiene muchos comentarios.
Así que hay 10 relojes en total. Contar de 0 a 256 llevaría entonces 256 * 10 = 2560relojes. Eso es 1000 relojes más que en mi código.
Tiene un número predecible de ciclos de reloj, sin importar si tiene un acarreo a mitad de camino o no. Y es más corto que su código en caso de acarreo.
No entiendo. Mi ejemplo también tiene un número predecible de ciclos de reloj. 255 veces toma (2+1+1+2)ciclos y 1 vez toma (2+1+2+2+2+1+2)ciclos. No estoy buscando un código de aspecto corto, sino rápido: o)
No, el tiempo de ejecución de su código es diferente en caso de que su byte bajo se desborde. Por lo tanto tienes 2 puntos de salida ( reti)
Sí, es cierto, pero este hecho lo hace más rápido o no? Su código siempre toma 2560 ciclos de reloj y el mío 1542.
¿Por qué usaría dos bytes si solo quiere contar hasta 256?
Porque debo usar 2 bytes si quiero contar valores mayores a 255. De hecho quiero contar hasta 65535 pero también en este caso mi código también toma menos ciclos de reloj (394752) y el tuyo 655360. Disculpa si no entiendo algo.
Hmm .. creo que puede tener razón. Además, ganaría más porque solo tiene un único registro para presionar/abrir.
Tenga en cuenta que no coloca las banderas de la CPU en la pila (SREG en mi respuesta), eso puede afectar su ciclo principal.
Pero podría usar r24 y r25 solo para este propósito, así que no tengo que cargarlo y almacenarlo. Eso me ahorraría 8 ciclos de reloj, ¿no? Así que solo tenía adiw r24 , 0x01que solo tomaría 2 ciclos. Eso tomaría 131072 ciclos de reloj para contar de 0 a 65535.
Esa es una posibilidad, sí, y la ventaja de usar ensamblaje. Aún así, las banderas de su CPU cambiarán en cada interrupción, lo que puede afectar su bucle principal.
Si eso no es un problema, entonces hay algunos AVR que tienen espacio para 2 instrucciones en la tabla de vectores de interrupción. Además de eso, si no usa la siguiente entrada en esa tabla, puede usarla usted mismo de todos modos. Entonces, con un poco de suerte, puede incluir toda la rutina de interrupción en la tabla de vectores. Te ahorra una rama (2 relojes).