Сборка AVR: самый быстрый способ увеличить два объединенных байта

Какой может быть самый быстрый способ увеличить два комбинированных байта на ассемблере (при условии, что я работаю на 8-битном процессоре)? В настоящее время я делаю это:

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

Итак, в сумме есть

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

для подсчета от 0 до 256 (255 раз (2+1+1+2), потому что нет переполнения, плюс 1 раз (2+1+2+2+2+1+2), когда происходит переполнение).

Верен ли мой расчет и есть ли более быстрый способ?

Ответы (1)

Побольше доверяйте своему компилятору. Напишите код на C, скомпилируйте его и посмотрите на дизассемблирование. Не уверен, какой набор инструментов вы используете, но avr-gcc создает довольно хорошо оптимизированный код.

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

Вы можете дизассемблировать файл .elf с помощью следующей команды (при условии, что вы используете набор инструментов gcc):

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

Кстати: вам, вероятно, нужно заранее поместить используемые регистры в стек, а затем вытолкнуть их (по 2 цикла каждый). Также помните, что прерывание (включая reti) длится не менее 8 тактовых циклов, кроме выполняемых инструкций.

; 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
Или можно дать avr-gccаргумент для вывода дизассемблирования в процессе компиляции.
Лично я думаю, что avr-gcc генерирует беспорядочный листинг, хотя он содержит много комментариев.
Итак, всего 10 часов. Подсчет от 0 до 256 занял бы 256 * 10 = 2560часы. Это на 1000 часов больше, чем в моем коде.
Он имеет предсказуемое количество тактовых циклов, независимо от того, есть ли у вас перенос на полпути или нет. И короче вашего кода на случай переноса.
Я не понимаю. Мой пример также имеет предсказуемое количество тактов. 255 раз требуется (2+1+1+2)циклов и 1 раз требуется (2+1+2+2+2+1+2)циклов. Я ищу не короткий код, а быстрый: o)
Нет, время выполнения вашего кода отличается в случае переполнения младшего байта. Следовательно, у вас есть 2 точки выхода ( reti)
Да, это так, но этот факт делает его быстрее или нет? Ваш код всегда занимает 2560 тактов, а мой 1542.
Зачем вам использовать два байта, если вы хотите считать только до 256?
Потому что я должен использовать 2 байта, если я хочу считать значения больше 255. На самом деле я хочу считать до 65535, но также и в этом случае мой код также занимает меньше тактов (394752), а ваш 655360. Извините, если я неправильно понимаю что-нибудь.
Хм.. думаю может ты и прав. Кроме того, вы получите больше, потому что у вас есть только один регистр для push/pop.
Обратите внимание, что вы не помещаете флаги ЦП в стек (SREG в моем ответе), что может повлиять на ваш основной цикл.
Но я мог бы использовать r24 и r25 только для этой цели, поэтому мне не нужно их загружать и хранить. Это сэкономило бы мне 8 тактов, не так ли? Так что мне нужно было всего adiw r24 , 0x01лишь 2 цикла. Для счета от 0 до 65535 потребуется 131072 тактовых цикла.
Это возможность, да, и преимущество использования сборки. Тем не менее, ваши флаги ЦП будут меняться при каждом прерывании, что может повлиять на ваш основной цикл.
Если это не проблема, то есть некоторые AVR, у которых есть место для 2 инструкций в таблице векторов прерываний. Кроме того, если вы не используете следующую запись в этой таблице, вы все равно можете использовать ее самостоятельно. Так что, если повезет, вы сможете уместить всю процедуру обработки прерывания в векторную таблицу. Сохраняет вам ветку (2 часа).