Forum: Mikrocontroller und Digitale Elektronik AVR Inline Assembler - Problem mit Funktionsparameter


von Daniel K. (daniel_k80)


Lesenswert?

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:
1
static inline void SysClock_SetClockSource(uint8_t Source)
2
 {
3
   ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
4
   {
5
    // Load CCP with 0xD8
6
    asm("LDI R31, 0x00" ); 
7
    asm("LDI R18, 0xD8" ); 
8
    asm("LDI R30, 0x34" ); 
9
    asm("STD Z+0, R18"  ); 
10
    // Switch clock source
11
12
    asm("LDI R31, 0x00" );
13
    asm("LDI R30, 0x40" ); 
14
    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

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

@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.
8
 */
9
void CCPWrite( volatile uint8_t * address, uint8_t value )
10
{
11
  volatile uint8_t * tmpAddr = address;
12
#ifdef RAMPZ
13
  RAMPZ = 0;
14
#endif
15
  asm volatile(
16
    "movw r30,  %0"        "\n\t"
17
    "ldi  r16,  %2"        "\n\t"
18
    "out   %3, r16"        "\n\t"
19
    "st     Z,  %1"       "\n\t"
20
    :
21
    : "r" (tmpAddr), "r" (value), "M" (CCP_IOREG_gc), "i" (&CCP)
22
    : "r16", "r30", "r31"
23
    );
24
}

von Jim M. (turboj)


Lesenswert?

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...

von Daniel K. (daniel_k80)


Lesenswert?

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.
8
>  */
9
> void CCPWrite( volatile uint8_t * address, uint8_t value )
10
> {
11
>   volatile uint8_t * tmpAddr = address;
12
> #ifdef RAMPZ
13
>   RAMPZ = 0;
14
> #endif
15
>   asm volatile(
16
>     "movw r30,  %0"        "\n\t"
17
>     "ldi  r16,  %2"        "\n\t"
18
>     "out   %3, r16"        "\n\t"
19
>     "st     Z,  %1"       "\n\t"
20
>     :
21
>     : "r" (tmpAddr), "r" (value), "M" (CCP_IOREG_gc), "i" (&CCP)
22
>     : "r16", "r30", "r31"
23
>     );
24
> }
25
>

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?)

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

@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.

von Daniel K. (daniel_k80)


Lesenswert?

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
1
  asm volatile(
2
  "movw r30,  %0"        "\n\t"
3
  "ldi  r16,  %2"        "\n\t"
4
  "out   %3, r16"        "\n\t"
5
  "st     Z,  %1"       "\n\t"
6
  :: "r" (CLK.CTRL), "r" (CLK_SCLKSEL_RC32M_gc), "M" (CCP_IOREG_gc), "i" (&CCP): "r16", "r30", "r31");
1
asm volatile("movw r30,  %0" :: "r" (CLK.CTRL));
2
asm volatile("ldi  r16,  %0" :: "M" (CCP_IOREG_gc) : "r16");
3
asm volatile("out   %0, r16" :: "i" (&CCP));
4
asm volatile("st     Z,  %0" :: "r" (CLK_SCLKSEL_RC32M_gc));

bekomme ich unterschiedliche Ergebnisse aus dem Disassembler.  Wenn ja, 
warum ist das so?

: Bearbeitet durch User
von Apollo M. (Firma: @home) (majortom)


Lesenswert?

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

: Bearbeitet durch User
von Daniel K. (daniel_k80)


Lesenswert?

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:
1
  asm volatile(
2
  "movw r30,  %0"        "\n\t"
3
  "ldi  r16,  %2"        "\n\t"
4
  "out   %3, r16"        "\n\t"
5
  "st     Z,  %1"       "\n\t"
6
  :: "r" (CLK.CTRL), "r" (CLK_SCLKSEL_RC32M_gc), "M" (CCP_IOREG_gc), "i" (&CCP));
1
0000010C  PUSH R28    Push register on stack 
2
0000010D  PUSH R29    Push register on stack 
3
0000010E  IN R28,0x3D    In from I/O location 
4
0000010F  IN R29,0x3E    In from I/O location 
5
  :: "r" (CLK.CTRL), "r" (CLK_SCLKSEL_RC32M_gc), "M" (CCP_IOREG_gc), "i" (&CCP));
6
00000110  LDI R24,0x40    Load immediate 
7
00000111  LDI R25,0x00    Load immediate 
8
00000112  MOVW R30,R24    Copy register pair 
9
00000113  LDD R18,Z+0    Load indirect with displacement 
10
  asm volatile(
11
00000114  LDI R24,0x01    Load immediate 
12
00000115  LDI R25,0x00    Load immediate 
13
00000116  MOVW R30,R18    Copy register pair 
14
00000117  LDI R16,0xD8    Load immediate 
15
00000118  OUT 0x34,R16    Out to I/O location 
16
00000119  STD Z+0,R24    Store indirect with displacement 
17
0000011A  LDI R24,0x00    Load immediate 
18
0000011B  LDI R25,0x00    Load immediate 
19
}
20
0000011C  POP R29    Pop register from stack 
21
0000011D  POP R28    Pop register from stack 
22
0000011E  RET     Subroutine return

Und dann einmal "aufgesplittet":
1
asm volatile("movw r30,  %0" :: "r" (CLK.CTRL));
2
asm volatile("ldi  r16,  %0" :: "M" (CCP_IOREG_gc));
3
asm volatile("out   %0, r16" :: "i" (&CCP));
4
asm volatile("st     Z,  %0" :: "r" (CLK_SCLKSEL_RC32M_gc));
1
{/*
2
0000010C  PUSH R28    Push register on stack 
3
0000010D  PUSH R29    Push register on stack 
4
0000010E  IN R28,0x3D    In from I/O location 
5
0000010F  IN R29,0x3E    In from I/O location 
6
asm volatile("movw r30,  %0" :: "r" (CLK.CTRL));
7
00000110  LDI R24,0x40    Load immediate 
8
00000111  LDI R25,0x00    Load immediate 
9
00000112  MOVW R30,R24    Copy register pair 
10
00000113  LDD R24,Z+0    Load indirect with displacement 
11
00000114  MOVW R30,R24    Copy register pair 
12
asm volatile("ldi  r16,  %0" :: "M" (CCP_IOREG_gc));
13
00000115  LDI R16,0xD8    Load immediate 
14
asm volatile("out   %0, r16" :: "i" (&CCP));
15
00000116  OUT 0x34,R16    Out to I/O location 
16
asm volatile("st     Z,  %0" :: "r" (CLK_SCLKSEL_RC32M_gc));
17
00000117  LDI R24,0x01    Load immediate 
18
00000118  LDI R25,0x00    Load immediate 
19
00000119  STD Z+0,R24    Store indirect with displacement 
20
0000011A  LDI R24,0x00    Load immediate 
21
0000011B  LDI R25,0x00    Load immediate 
22
}
23
0000011C  POP R29    Pop register from stack 
24
0000011D  POP R28    Pop register from stack 
25
0000011E  RET     Subroutine return

Interessant sind diese beiden Stellen hier:
1
00000117  LDI R16,0xD8    Load immediate 
2
00000118  OUT 0x34,R16    Out to I/O location 
3
00000119  STD Z+0,R24    Store indirect with displacement 
4
0000011A  LDI R24,0x00    Load immediate 
5
0000011B  LDI R25,0x00    Load immediate 
6
--------------------------------------
7
00000115  LDI R16,0xD8    Load immediate 
8
00000116  OUT 0x34,R16    Out to I/O location 
9
00000117  LDI R24,0x01    Load immediate 
10
00000118  LDI R25,0x00    Load immediate 
11
00000119  STD Z+0,R24    Store indirect with displacement 
12
0000011A  LDI R24,0x00    Load immediate 
13
0000011B  LDI R25,0x00    Load immediate

Wie lässt sich die umgestellte Reihenfolge für das Laden des Wertes 0x01 
erklären?

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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
>
>
1
>   asm volatile(
2
>   "movw r30,  %0"        "\n\t"
3
>   "ldi  r16,  %2"        "\n\t"
4
>   "out   %3, r16"        "\n\t"
5
>   "st     Z,  %1"       "\n\t"
6
>   :: "r" (CLK.CTRL), "r" (CLK_SCLKSEL_RC32M_gc), "M" (CCP_IOREG_gc), "i" 
7
> (&CCP): "r16", "r30", "r31");
8
>
>
>
1
> asm volatile("movw r30,  %0" :: "r" (CLK.CTRL));
2
> asm volatile("ldi  r16,  %0" :: "M" (CCP_IOREG_gc) : "r16");
3
> asm volatile("out   %0, r16" :: "i" (&CCP));
4
> asm volatile("st     Z,  %0" :: "r" (CLK_SCLKSEL_RC32M_gc));
5
>
>
> 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.
8
>  */
9
> void CCPWrite( volatile uint8_t * address, uint8_t value )
10
> {
11
>   volatile uint8_t * tmpAddr = address;
12
> #ifdef RAMPZ
13
>   RAMPZ = 0;
14
> #endif
15
>   asm volatile(
16
>     "movw r30,  %0"        "\n\t"
17
>     "ldi  r16,  %2"        "\n\t"
18
>     "out   %3, r16"        "\n\t"
19
>     "st     Z,  %1"       "\n\t"
20
>     :
21
>     : "r" (tmpAddr), "r" (value), "M" (CCP_IOREG_gc), "i" (&CCP)
22
>     : "r16", "r30", "r31"
23
>     );
24
> }
25
>

hmmm.  Das würde man eher so schreiben:
1
static inline __attribute__((__always_inline__))
2
void CCPWrite (volatile uint8_t *address, uint8_t value)
3
{
4
  __asm volatile (
5
    "out %i[ccp], %[ccpval]"        "\n\t"
6
    "st  %a[addr], %[val]"
7
    : /* no outputs */
8
    : [addr]   "z" (address),
9
      [val]    "r" (value),
10
      [ccpval] "r" ((uint8_t) CCP_IOREG_gc),
11
      [ccp] "i" (&CCP)
12
    : "memory");
13
}

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:
1
#ifdef RAMPZ
2
    "out __RAMPZ__, ..."        "\n\t"
3
#endif
4
    ...
5
#ifdef RAMPD // sic!
6
    "out __RAMPZ__, __zero_reg__"
7
#endif

: Bearbeitet durch User
von Daniel K. (daniel_k80)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.