Hallo zusammen,
ich versuche gerade krampfhaft meine Treiberbibliothek für das
Clocksystem des XMega256A3BU zu optimieren. Ich habe festgestellt, dass
die Befehle
1
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
2
{
3
CCP = CCP_IOREG_gc;
4
CLK.CTRL = CLK_SCLKSEL_RC32M_gc;
5
}
mit dem Optimierungslevel -O0 nicht funktionieren und ich mind. -O1
verwenden muss (warum eigentlich? Weiß das jemand? Alternativ schaue ich
mir das die Tage mal im Disassembler an). Allerdings ist diese
Optimierung fürs Debugging schwierig...
Daher will ich solche Blöcke durch Inline Assembler ersetzen. Von
Assembler habe ich leider recht wenig Ahnung und vom C-Compiler noch
weniger...
Bisher habe ich mir diesen Block zusammengebastelt:
asm("LDI R18, 0x02" ); // <------- Hier muss der Funktionsparameter "Source" rein
15
asm("STD Z+0, R18" );
16
}
Wenn ich nun die Zeile
1
asm("LDI R18, 0x02" )
mit dem Wert 0x04 füttere (das entspricht der PLL als Clocksource)
funktioniert der Code. Jetzt möchte ich die Clocksource aber per
Funktion ändern. Wie bekomme ich den Funktionsparameter "Source" in den
Assembler Block? Ich hatte gestern gelesen, dass die Übergabeparameter
bei R24 stehen. Wenn ich aber nun
1
asm("STD Z+0, R18" );
durch
1
asm("STD Z+0, R24" );
und die Funktion mit
1
SysClock_SetClockSource(0x04);
aufrufe kommt nicht der selbe Takt raus, wie als wenn ich den Wer 0x04
ins Register R18 schreibe und das Register kopiere.
Habe ich da irgendwo falsche Informationen oder einen Denkfehler?
Vielen Dank für die Hilfe.
Gruß
Daniel
@Daniel K. (daniel_k80)
>mit dem Optimierungslevel -O0 nicht funktionieren und ich mind. -O1>verwenden muss (warum eigentlich? Weiß das jemand?
Weil nicht die optimale Befehlssequenz erzeugt wird sondern ziemlich
aufwändiger ASM-Quark.
>Daher will ich solche Blöcke durch Inline Assembler ersetzen.
Ist nicht nötig, es gibt dafür ein passendes Macro.
1
/*! \brief CCP write helper function written in assembly.
2
*
3
* This function is written in assembly because of the timecritial
4
* operation of writing to the registers.
5
*
6
* \param address A pointer to the address to write to.
7
* \param value The value to put in to the register.
Die Funktion wäre in einem Assembler File besser aufgehoben, da 100%
Assembler.
Die Inline assembly Syntax in gcc ist komplex, weil man benutzte
Register angeben muss und auch wie Paremeter übergeben werden können.
IMO fehlt da auch ein "volatile", ohne das der Optimizer gerne was
rausschmeissen kann.
Aber auch im direkten Assembler File müsste man das C ABI beachten, was
einem vorgibt welche Register man verändern darf und welche nicht. D.h.
Du müsstest u.U. Register vorher auf den Stack sichern und hinterher
wiederherstellen.
Leider kenne ich mich da bei AVR überhaupt nicht aus...
Hallo,
Falk B. schrieb:> @Daniel K. (daniel_k80)>>>mit dem Optimierungslevel -O0 nicht funktionieren und ich mind. -O1>>verwenden muss (warum eigentlich? Weiß das jemand?>> Weil nicht die optimale Befehlssequenz erzeugt wird sondern ziemlich> aufwändiger ASM-Quark.>>>Daher will ich solche Blöcke durch Inline Assembler ersetzen.>> Ist nicht nötig, es gibt dafür ein passendes Macro.>>
1
>/*! \brief CCP write helper function written in assembly.
2
> *
3
> * This function is written in assembly because of the timecritial
4
> * operation of writing to the registers.
5
> *
6
> * \param address A pointer to the address to write to.
7
> * \param value The value to put in to the register.
Danke für den Denkanstoß und für das Makro. Das hilft mir beides gleich
in doppelter Weise weiter. Den Code schaue ich mir die Tage dennoch mal
im Disassembler an, weil ich einfach mal den Vergleich sehen möchte :)
(hat das Atmel Studio einen Disassembler oder muss ich einen
downloaden?)
Aber ist dennoch komisch, dass der Compiler aus zwei Registerzuweisungen
CCP = 0xD8
SYS.CTRL = 0x04
Soviel Overhead erzeugt, dass SYS.CTRL = ... mehr als 4 Zyklen braucht
Das Makro werde ich mir mal anschauen, alleine um herauszufinden wo ich
was falsch gemacht haben könnte.
Jim M. schrieb:> Die Funktion wäre in einem Assembler File besser aufgehoben, da 100%> Assembler.>> Die Inline assembly Syntax in gcc ist komplex, weil man benutzte> Register angeben muss und auch wie Paremeter übergeben werden können.> IMO fehlt da auch ein "volatile", ohne das der Optimizer gerne was> rausschmeissen kann.>> Aber auch im direkten Assembler File müsste man das C ABI beachten, was> einem vorgibt welche Register man verändern darf und welche nicht. D.h.> Du müsstest u.U. Register vorher auf den Stack sichern und hinterher> wiederherstellen.> Leider kenne ich mich da bei AVR überhaupt nicht aus...
Da hast du natürlich Recht. Ich hatte mir gestern halt nur gedacht, dass
die Verwendung eines Assembler-Blocks in einer C-Funktion einige Arbeit
abnimmt, dem war aber nicht so.
Die Verwendung von Assembler ist nicht einmal das ärgerlichste. Das
Ärgerlichste war, dass ich den halben Tag nach einem Fehler gesucht
habe, warum die PLL nicht verwendet wird (und nen Tag vorher hatte ich
auch Probleme bei dem Batteriebackup-System, wo auch das CCP Register
verwendet wird - 100%ig das selbe Problem...). Und die CCP geschützten
Register kann man ja auch nicht mal vernünftig mit nem JTAG auslesen...
(oder?)
@Daniel K. (daniel_k80)
>in doppelter Weise weiter. Den Code schaue ich mir die Tage dennoch mal>im Disassembler an, weil ich einfach mal den Vergleich sehen möchte :)>(hat das Atmel Studio einen Disassembler
Sicher. Einfach auf Debug gehen, dann gibt es irgendwo ein Fenster, das
man einblenden kann.
>Aber ist dennoch komisch, dass der Compiler aus zwei Registerzuweisungen>CCP = 0xD8>SYS.CTRL = 0x04>Soviel Overhead erzeugt, dass SYS.CTRL = ... mehr als 4 Zyklen braucht
Ist aber so. Kann man sehr leicht im .lss file sehen, dort steht der
ASM-Code deines Compilats, licht im Unterordner /Default bzw. der Name
deines Compiler-Setups.
Hallo,
macht es einen Unterschied vom Ergebnis ob ich einen einzelnen asm()
Befehl für mehrere Instruktionen verwende oder ob ich die aufteile? Weil
wenn ich diese beiden Codes compiliere
Daniel K. schrieb:> ): "r16", "r30", "r31");
ich denke ja, weil die anweisungen nicht identisch sind bzgl. der
clobber info die der compiler bekommt. ich rate mal, die kompakte
anweisung erzeugt mehr code?! aber nur wenn der makierte part stehen
bleibt. wenn der raus fällt muss es identisch sein. so werden wohl die
register auf dem stack gesichert ...
mt
Hallo,
ok, das dachte ich mir bereits. Ich habe aber nur gelesen, dass diese
Infos für den Compiler sind, damit der weiß welche Register sich ändern.
Greift dann eine zusätzliche Optimierung, wenn er diese Infos hat?
Edit: Habe es nun mal ohne clobber probiert und ich bekomme folgendes
Ergebnis:
Daniel K. schrieb:> Hallo,>> macht es einen Unterschied vom Ergebnis ob ich einen einzelnen asm()> Befehl für mehrere Instruktionen verwende oder ob ich die aufteile?
Ja.
> Weil wenn ich diese beiden Codes compiliere>>
>> bekomme ich unterschiedliche Ergebnisse aus dem Disassembler.> Wenn ja, warum ist das so?
Weil es unterschiedlicher Code ist. Zudem haben die einzelnen asms nix
miteinander zu tun. Auch wenn Code erzeugt wird wie du erwartet,
bedeutet das nicht, dass dies IMMER der Fall sein muss. D.h. dein Code
ist nicht robust, und wenn sich anderer Code ändert (z.B.
Registerallokation) dann können sich deine frei flottierenden asms
plötzlich anders verhalten — zwar immer noch korrekt, aber eben nicht
mehr so was du es gerne möchtest :-)
Falk B. schrieb:> Ist nicht nötig, es gibt dafür ein passendes Macro.>>
1
>/*! \brief CCP write helper function written in assembly.
2
> *
3
> * This function is written in assembly because of the timecritial
4
> * operation of writing to the registers.
5
> *
6
> * \param address A pointer to the address to write to.
7
> * \param value The value to put in to the register.
1) Für CCP wird OUT verwendet, daher %i als Modifier.
2) RAMPZ = 0 vwerstehe ich nicht: avr-gcc geht davon aus, dass RAMPZ
immer den Wert 0 enthält. Ist das nicht der Fall, hat man ein
nicht-funktionales Programm. Daher kann RAMPZ = 0 entfallen.
3) Register und Werte händlisch zuzuweisen nicht nicht notwendig.
Ohne "static inline" ergibt das folgenden Code:
1
CCPWrite:
2
ldi r18,lo8(-40)
3
movw r30,r24
4
/* #APP */
5
out __CCP__, r18
6
st Z, r22
7
/* #NOAPP */
8
ret
Falls in RAMPZ ein Wert != 0 gebraucht wird, dann würd ich das IM asm
erledigen:
Ah ok. Ich dachte es wäre das gleiche ob ich eine einzelne asm-Anweisung
mit vier Befehlen oder vier asm-Anweisungen mit je einem Befehl
verwende.
Die Antworten habe mir auf jeden Fall schon mal geholfen :)
Danke nochmals an alle.
Daniel K. schrieb:> Ah ok. Ich dachte es wäre das gleiche ob ich eine einzelne asm-Anweisung> mit vier Befehlen oder vier asm-Anweisungen mit je einem Befehl> verwende.
Nein.
asm("LDI R30, 0x40" );
asm("LDI R18, 0x02" );
asm("STD Z+0, R18" );
1. Fehler: R30 wird gesetzt, ohne dass der entsprechende Seiteneffekt
beschrieben ist. GCC geht also davon aus, dass sich der Wert in R30
nicht ändert und könnte einen Wert darin gespreichert haben, der durch
das asm zerstört wird.
2. Fehler: R30 wird im 1. asm nicht verwendet, GCC könnte / dürfte also
nach dem 1. asm einen Wert in R30 speichern und 0x40 zerstören.
3. Fehler: Dito für R18.
4. Fehler: Das 3. asm verwendet Z und R18, ohne dass diese im asm oder
durch Operanden / Constraints gesetzt wurden. Siehe Erklärung zu 2.
5. Fehler: Das 3. asm verändert den Speicher, ohne dass dies beschrieben
wurde.