7
\$\begingroup\$

What is the fastest way to increment two combined bytes in assembler (assuming I'm working on an 8-bit CPU)? Currently I'm doing this:

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

So in sum there are

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

to count from 0 to 256 (255 times (2+1+1+2) because no overflow plus 1 time (2+1+2+2+2+1+2) when overflow occurs).

Is my calculation correct and is there a faster way?

\$\endgroup\$

2 Answers 2

8
\$\begingroup\$

Have a bit more trust in your compiler. Write the code in C, compile it and look at the disassembly. Unsure which toolchain you use, but avr-gcc creates pretty well optimized code.

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

You can disassemble the .elf file with the following command (provided you use the gcc toolchain):

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

BTW: You probably need to push the used registers to stack beforehand and pop them afterwards (2 cycles each). Also remember that an interrupt (including reti) lasts at least 8 clock cycles apart from the instructions being executed.

; 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
\$\endgroup\$
14
  • \$\begingroup\$ Or you can give avr-gcc an argument to output disasembly in the compilation process. \$\endgroup\$ Commented Apr 7, 2013 at 20:58
  • \$\begingroup\$ Personally I think avr-gcc is generating a messy listing, it does contain lots of comments though. \$\endgroup\$
    – jippie
    Commented Apr 7, 2013 at 21:00
  • \$\begingroup\$ So there are 10 clocks in sum. Counting from 0 to 256 would take then 256 * 10 = 2560 clocks. That's 1000 clocks more than in my code. \$\endgroup\$
    – arminb
    Commented Apr 7, 2013 at 21:11
  • 1
    \$\begingroup\$ That is a possibility, yes, and the advantage for using assembly. Still your CPU flags will change on every interrupt, which may affect your main loop. \$\endgroup\$
    – jippie
    Commented Apr 7, 2013 at 21:39
  • 1
    \$\begingroup\$ If that is not a problem, then there are some AVR's that have room for 2 instructions in the interrupt vector table. On top of that, if you don't use the next entry in that table you can use it yourself anyway. So with a bit of luck you can fit the entire interrupt routine in the vector table. Saves you a branch (2 clocks). \$\endgroup\$
    – jippie
    Commented Apr 7, 2013 at 21:45
1
\$\begingroup\$

When resorting to assembler,

  • still value readable / maintainable higher than saved two bytes. Or cycles.
    Unless that saving makes what otherwise would be broken.
  • try to get things done without ado.
  • check with a decent compiler if it doesn't do better.
    godbolt is a valuable resource here.

A stab or three at incrementing a two byte integral value in an ISR:

OVF1_handler:          ; TIMER1 overflow ISR
    push r21           ; 1 cycle (2 if AVRe)
    in   r21, SREG     ; 1 cycle
    push r21           ; 1 cycle (2 if AVRe)

    lds  r21, timerhl  ; load low byte into working register; 2 cycles
    subi r21, -1       ; 1 cycle
    sts  timerhl, r21  ; write value back to variable; 2 cycles
#if !BEST_WORST_CASE  // best worst case cycles: process carry unconditionally
# if !BEST_PREDOMINANT_CASE    // no duplicated epilogue, 7/11 cycles
    brcs OVF1_epilogue ; 1 cycle if no carry, else 2
# else // BEST_PREDOMINANT_CASE  // duplicated epilogue, 6/12 cycles
    brcc OVF1_overflow ; 1 cycle if carry, else 2
    pop  r21
    out  SREG, r21
    pop  r21
    reti               ; we're done
# endif
#endif

OVF1_overflow:         ; in case lower byte overflowed
    lds  r21, timerhh  ; load high byte into working register; 2 cycles
    sbci r21, -1       ; process carry; 1 cycle - no faster than addiw
    sts  timerhh, r21  ; write value of high byte back to variable; 2 cycles

OVF1_epilogue:
    pop  r21           ; 2 cycles
    out  SREG, r21     ; 1 cycle
    pop  r21           ; 2 cycles
    reti               ; we're done

most of any advantage over using addiw comes from not saving&restoring a second register.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.