-
Hier mal ein schöner Vergleich:
Code:
uint16_t fmac16(uint16_t op1, uint16_t op2, uint16_t op3)
{
uint16_t result;
// range checkto prevent overflow. It is necessary that at least one
// operand's high byte is != 0x80
op1 = (op1 != 0x8000)
? (op2 = (op2 != 0x80) ? op2 : 0x81), op1
: 0x8100;
asm volatile (
"fmuls %B1, %B2" "\n\t" // take high byte of op1 and op2 only
"add r0, %A3" "\n\t" // do a 16 bit add
"adc r1, %B3" "\n\t"
"movw %0, r0" "\n\t"
"brvc 0f" "\n\t" // check for overflow (pos/neg) @see fadd8
"ldi %A0, 0xff" "\n\t"
"ldi %B0, 0x7f" "\n\t"
"brcc 0f" "\n\t"
"ldi %A0, 0x00" "\n\t"
"ldi %B0, 0x80" "\n\t"
"0:" "\n\t"
: "=&a" (result)
: "a" (op1), "a" (op2), "a" (op3)
: "r0", "r1"
);
return result;
}
Für die eigentliche MAC Operation werde hier nur 5 Takte benötigt (fmul, add, adc und movw).
Eine direkte Implementierung in C
Code:
uint16_t op161;
uint16_t op162;
uint16_t op163;
uint16_t result16;
op161 = 0x8000;
op162 = 0x8000;
op163 = 0x7000;
result16 = op161*op162+op163; // <- Das hier
Ist als Assembler Code schon erheblich länger:
Code:
mul r18,r24
movw r20,r0
mul r18,r25
add r21,r0
mul r19,r24
add r21,r0
clr r1
movw r18,r20
ldd r24,Y+9
ldd r25,Y+10
add r24,r18
adc r25,r19
und benötigt 17 Takte, wobei die +/- Überlaufprüfung nochmal erheblich komplexer wäre.
-
Ein Fehler ist noch drinne: R1 muss nach einer Funktion/Assembler-Schnippsel unverändert vorliegen, denn avr-gcc hält immer 0 in diesem Register!
Wenn du es also veränderst, musst du die 0 wieder herstellen, sonst bekommst du an ganz anderer Stelle einen Fehler.
-
Super, danke für den Hinweis. Bevor ich mich jetzt wirder durchs .S File quäle: Reicht es nicht, r1 in der Clobber Liste anzugeben?
-
Nein, das genügt nicht. Sonst hätt ich nix dazu geschrieben ;-)
Aus den Clobbers kann es also raus. R0 ebenfalls, da wird eh von ausgegangen, daß das nichts überlebt.
-
OK, also push r1, pop r1 und gut ist.
-
Was ist dann der Effekt, wenn ich ein Register (z.B. r1) in der Clobber Liste angebe?
-
AFAIK sind R0 und R1 fided regs, werden also nicht vom Compiler verwendet.
Um genau zu sein: Der Compiler reloadet z.B. keine Variablen in diese Register oder verwendet sie für Funktionsargumente oder zum temporären Speichern. Dennoch werden sie im avr-gcc-Backend verwendet, etwa wenn ein int gegen eine Konstande kleiner als 256 verglichen wird (Das highbyte wird dann mit cpc __zero_reg__ verglichen).
Der Compiler selbst hat aber auf interner Ebene keine "Vorstellung" von diesen Registern, sondern dieser Verwendung als temp bzw. 0-Register geschehen implizit durch die Backend-Implementierung.
Alles klar? Wahrscheinlich noch verwirrter als vorher...
Register > 1 in der Clobber-Liste haben hatürlich einen Effekt. Mach mal eine Funktion und clobbere r2 und schau den Code an.
-
Danke jetzt ist klar. Habe r1 am Anfang der mul auf den Stack gelegt und nacher wieder restauriert. Sind halt zwei Takte mehr.
-
Ein clr braucht einen Takt, während push/pop (Speicherzugriff!) 4 Takte braucht. Zudem braucht es 1 Byte vom Stack und das doppelte an Flash...
Einfach
clr r1
oder
clr __zero_reg__
-