AVR-Assembly: Der schnellste Weg, um zwei kombinierte Bytes zu inkrementieren

Was könnte der schnellste Weg sein, um zwei kombinierte Bytes in Assembler zu erhöhen (vorausgesetzt, ich arbeite an einer 8-Bit-CPU)? Aktuell mache ich das:

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

Also in Summe gibt es

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

von 0 bis 256 zu zählen (255 Mal (2+1+1+2), weil kein Überlauf plus 1 Mal (2+1+2+2+2+1+2), wenn ein Überlauf auftritt).

Ist meine Berechnung korrekt und gibt es einen schnelleren Weg?

Antworten (1)

Haben Sie etwas mehr Vertrauen in Ihren Compiler. Schreiben Sie den Code in C, kompilieren Sie ihn und schauen Sie sich die Disassemblierung an. Sie sind sich nicht sicher, welche Toolchain Sie verwenden, aber avr-gcc erstellt ziemlich gut optimierten 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

Sie können die .elf-Datei mit dem folgenden Befehl disassemblieren (vorausgesetzt, Sie verwenden die gcc-Toolchain):

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

Übrigens: Sie müssen wahrscheinlich die verwendeten Register vorher stapeln und danach platzen lassen (jeweils 2 Zyklen). Denken Sie auch daran, dass ein Interrupt (einschließlich reti) mindestens 8 Taktzyklen dauert, abgesehen von den ausgeführten Anweisungen.

; 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
Oder Sie können avr-gccein Argument angeben, um die Disassemblierung im Kompilierungsprozess auszugeben.
Persönlich denke ich, dass avr-gcc eine chaotische Auflistung erzeugt, es enthält jedoch viele Kommentare.
Also insgesamt 10 Takte. Das Zählen von 0 bis 256 würde dann 256 * 10 = 2560Takte dauern. Das sind 1000 Takte mehr als in meinem Code.
Es hat eine vorhersehbare Anzahl von Taktzyklen, egal ob Sie auf halbem Weg einen Übertrag haben oder nicht. Und es ist kürzer als Ihr Code im Falle eines Übertrags.
Ich verstehe nicht. Mein Beispiel hat auch eine vorhersagbare Anzahl von Taktzyklen. 255 Mal dauert es (2+1+1+2)Zyklen und 1 Mal dauert es (2+1+2+2+2+1+2)Zyklen. Ich suche keinen kurz aussehenden Code, sondern den schnellsten :o)
Nein, die Ausführungszeit Ihres Codes ist anders, falls Ihr Low-Byte überläuft. Daher haben Sie 2 Ausgangspunkte ( reti)
Ja, das stimmt, aber diese Tatsache macht es schneller, oder nicht? Dein Code braucht immer 2560 Taktzyklen und meiner 1542.
Warum sollten Sie zwei Bytes verwenden, wenn Sie nur bis 256 zählen möchten?
Weil ich 2 Bytes verwenden muss, wenn ich auf Werte größer als 255 zählen möchte. Tatsächlich möchte ich bis 65535 zählen, aber auch in diesem Fall benötigt mein Code weniger Taktzyklen (394752) und Ihrer 655360. Entschuldigung, wenn ich falsch verstehe etwas.
Hmm .. denke du könntest Recht haben. Obendrein würden Sie extra gewinnen, weil Sie nur ein einziges Register zum Drücken/Knallen haben.
Beachten Sie, dass Sie die CPU-Flags nicht auf den Stapel schieben (SREG in meiner Antwort), was sich auf Ihre Hauptschleife auswirken kann.
Aber ich könnte r24 und r25 nur für diesen Zweck verwenden, damit ich es nicht laden und speichern muss. Das würde mir 8 Taktzyklen ersparen, oder? Also musste ich nur dazu, adiw r24 , 0x01was nur 2 Zyklen dauern würde. Das würde 131072 Taktzyklen dauern, um von 0 bis 65535 zu zählen.
Das ist eine Möglichkeit, ja, und der Vorteil für die Verwendung von Assembly. Dennoch ändern sich Ihre CPU-Flags bei jedem Interrupt, was sich auf Ihre Hauptschleife auswirken kann.
Wenn das kein Problem ist, dann gibt es einige AVRs, die Platz für 2 Befehle in der Interrupt-Vektortabelle haben. Darüber hinaus können Sie, wenn Sie den nächsten Eintrag in dieser Tabelle nicht verwenden, ihn trotzdem selbst verwenden. So passt mit etwas Glück die gesamte Interrupt-Routine in die Vektortabelle. Spart euch eine Verzweigung (2 Takte).