Forum: Compiler & IDEs Inline Assembler Wieso Contraints, ClobberList etc?


von AndyS (Gast)


Lesenswert?

Hallo,

Habe in meinem Programm die Zykluszeit gemessen und möchte jetzt weiter 
mittels inline Assembler optimieren.
1
#include <avr/io.h>
2
#include <util/delay.h>
3
#include "Zykluszeit.h"
4
5
int main(void)
6
{
7
  //Ein-Ausgabe Init
8
  DDRB = 255;
9
  DDRD |= (1<<DDD0); //Zykluszeit PIN
10
11
    while(1)
12
    {
13
    asm volatile(
14
    "in r16, PORTB"
15
      : "d,M"
16
    );    
17
  }
18
  return 1;
19
}

Compiler Fehler:
Error  1  expected '(' before ')' token

Woran liegt das?
Prinzipiell verstehe ich diese ganzen Contraints und In und Out List 
nicht. Wieso so kompliziert?

Gibt es eine Möglichkeit normalen Assembler inline zu codieren?
Die benutzten Register sichere ich via push pop und die Sache hat sich. 
Da braucht es doch keine %A0 oder %Dn oder was weiß ich was das heißt...

von Krapao (Gast)


Lesenswert?

> Gibt es eine Möglichkeit normalen Assembler inline zu codieren?
> Die benutzten Register sichere ich via push pop und die Sache hat sich.

Sicher, kannst du machen (oben machst du es aber nicht).

Der Vorteil der "was weiß ich was das heißt" ist, dass du dem C-Compiler 
die Hauptarbeit gibst.

Angenommen du willst den PORTB (wieso eigentlich PORTB und nicht PINB?) 
nicht dem Register r16 zuweisen, sondern du willst den PINB Wert einer 
deiner Variablen zuweisen.

Bei deiner Einfachstmethode weisst du bereits nicht, wie deine Variable 
anzusprechen ist... dafür brauchst du die Listen.

http://www.rn-wissen.de/index.php/Inline-Assembler_in_avr-gcc
http://www.nongnu.org/avr-libc/user-manual/inline_asm.html

von AndyS (Gast)


Lesenswert?

Krapao schrieb:
> Sicher, kannst du machen (oben machst du es aber nicht).
1
asm volatile("push r16");  
2
asm volatile("in r16, PORTD");  
3
asm volatile("pop r16");
Aber so funktioniert es auch nicht.
Wie normal soll ich es denn noch schreiben?

Krapao schrieb:
> (wieso eigentlich PORTB und nicht PINB?)

Eigentlich PORTD. Ich will keinen Eingangssignal lesen sondern einfach 
den PORT Zustand erfragen.

Krapao schrieb:
> sondern du willst den PINB Wert einer
> deiner Variablen zuweisen.

dann weise ich diesen vor mein Inline Assembler Code zu?
Wo liegt das Problem?

von (prx) A. K. (prx)


Lesenswert?

AndyS schrieb:

> Prinzipiell verstehe ich diese ganzen Contraints und In und Out List
> nicht. Wieso so kompliziert?

Weil dir der Compiler sonst allzu leicht einen Strich durch die Rechnung 
macht. Er muss wissen, welche Register von der Assembler-Sequenz in 
welcher Form verwendet oder verändert werden.

von AndyS (Gast)


Lesenswert?

A. K. schrieb:
> Weil dir der Compiler sonst allzu leicht einen Strich durch die Rechnung
> macht. Er muss wissen, welche Register von der Assembler-Sequenz in
> welcher Form verwendet oder verändert werden.

Habe jetzt folgendes überlegt:
1
unsigned char reg = 0;
2
while(1)
3
{
4
  unsigned char port = PORTD;
5
  asm volatile("in %0 %1" : "=d" (reg) : "I" (port));  
6
}

Wieso muss ich laut Tabelle
>in  r,I   input from I/O
für Input das Constraint I verwenden? I ist doch eine positive 6 Bit 
Konstante? PORTD ist aber 8 Bit breit, dh ich kann nicht alle bits darin 
darstellen.

Beim Kompilieren bekomme ich einen Compiler Fehler:
Warning  1  asm operand 1 probably doesn't match constraints
Error  2  impossible constraint in 'asm'

Ich habe auch schon _SFR_IO_ADDR(PORTD) probiert, jedoch nicht erkannt.

Eine Theoretische Frage die bei beiden Tutorials nicht direkt 
herauskommt: Wird jetzt ein Register mit dem Variablennamen reg belegt 
und zeigt diese 8 Bit Variable genau auf dieses eine Register in welches 
ich den PORT lade? Wenn ja,  kann ich dann festlegen in welches Register 
(r16-r31) diese Variable kommt oder lasst mich der Compiler insofern 
keine Entscheidung treffen?

von (prx) A. K. (prx)


Lesenswert?

AndyS schrieb:

> für Input das Constraint I verwenden? I ist doch eine positive 6 Bit
> Konstante? PORTD ist aber 8 Bit breit, dh ich kann nicht alle bits darin
> darstellen.

Es geht nicht um die Breite des Ports, sondern um die Breite der Adresse 
des Ports. Und die beträgt beim IN Befehl 6 Bits.

> Wenn ja,  kann ich dann festlegen in welches Register
> (r16-r31) diese Variable kommt oder lasst mich der Compiler insofern
> keine Entscheidung treffen?

Überlass das lieber dem Compiler. Auf R16-R31 hast du es ja schon selbst 
eingeschränkt (weshalb?).

von AndyS (Gast)


Lesenswert?

A. K. schrieb:
> Es geht nicht um die Breite des Ports, sondern um die Breite der Adresse
> des Ports. Und die beträgt beim IN Befehl 6 Bits.
1
unsigned char reg = 0;
2
    
3
while(1)
4
{  
5
    asm volatile( "in %0 %1" : "=d" (reg) : "I" _SFR_IO_ADDR(PORTD) );  
6
}
7
return 1;

Compiler Fehler:
Error  1  `,' required

Wenn ich auf den Fehler doppelklicke, dann passiert nichts.
Es fehlt aber kein ",".

A. K. schrieb:
> Auf R16-R31 hast du es ja schon selbst
> eingeschränkt (weshalb?).

Weil das immer noch Assemblercode sein soll und ich nicht will dass das 
alles der Compiler einteilt wie er möchte.

von AndyS (Gast)


Lesenswert?

AndyS schrieb:
> Wenn ich auf den Fehler doppelklicke, dann passiert nichts.
> Es fehlt aber kein ",".

Ok, es fehlt ein ,

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

AndyS schrieb:
>> Sicher, kannst du machen (oben machst du es aber nicht).
> asm volatile("push r16");
> asm volatile("in r16, PORTD");
> asm volatile("pop r16");
> Aber so funktioniert es auch nicht.

Wäre ja auch Blödsinn.
1
(void)PORTD;

hätte den gleichen Effekt: PORTD wird gelesen, aber das Ergebnis
danach verworfen.

Vielleicht erzählst du uns ja lieber, was du auf diese Weise
optimieren willst.  Das simple Einlesen eines Portregisters
bekommst du schließlich auch in deinem Assemblercode nicht schneller
hin als der C-Compiler (sofern man ihm die Optimierung einschaltet
natürlich).

von AndyS (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Vielleicht erzählst du uns ja lieber, was du auf diese Weise
> optimieren willst.  Das simple Einlesen eines Portregisters
> bekommst du schließlich auch in deinem Assemblercode nicht schneller
> hin als der C-Compiler (sofern man ihm die Optimierung einschaltet
> natürlich).

Das war nur ein Beispiel.
Ich wollte zwei Abfragen und Ausgaben in assembler abfragen und wollte 
den Unterschied zum C Compiler messen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

AndyS schrieb:

> Wieso so kompliziert?

Damit nur die es verwenden, der genau wissen, was sie tun.

Spaß beiseite.
1
#include <avr/io.h>
2
3
void foo (void)
4
{
5
    unsigned char reg;
6
    asm volatile ("in %0, %1" : "=r" (reg) : "I" (_SFR_IO_ADDR (PORTD)));
7
}

ab avr-gcc 4.7 geht dann auch das:
1
void foo_47 (void)
2
{
3
    unsigned char reg;
4
    asm volatile ("in %0, %i1" : "=r" (reg) : "n" (&PORTD));
5
}

von (prx) A. K. (prx)


Lesenswert?

AndyS schrieb:

> Prinzipiell verstehe ich diese ganzen Contraints und In und Out List
> nicht. Wieso so kompliziert?

Die Leistungsfähigkeit dieser Technik erschliesst sich erst dann, wenn 
man sich von klassischer Assembler-Programmung löst, und nicht zig oder 
hunderte Zeilen Assembler-Quelltext in C Code einzubetten versucht.

In GCC verwendet man Assemblercode beispielsweise in Form kleiner als 
eigenständige Inline-Funktion formulierter Häppchen aus einer oder 
weniger Zeilen. So kann man beispielsweise bestimmte Maschinenbefehle 
elegant für C verfügbar machen, wie Rotation oder Bitreverse.

Die Programmstuktur und den übrigen Code belässt man in C und ruft darin 
diese Häppchen auf. Eine eigene Registerverwaltung wird so unnötig und 
man kann die Vorzüge des C Optimizers auch für diese Assembler-Häppchen 
nutzen.

Man schreibt also keine Assembler-Funktion, die eine 
Bitreverse-Operation auf ein ganzes Array durchführt, sondern man 
schreibt eine C-Funktion, die dies in normalem C durchführt und darin 
die Bitreverse-Operation nutzt. Und mit etwas Glück erlebt man 
vielleicht, dass der Compiler das besser optimiert, als man es selbst 
geschrieben hätte, weil er auf die Schleife unrolling anwendet und man 
hinterher im Code nicht einen Bitreverse-Befehl findet, sondern 4 
verzahnte.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

AndyS schrieb:
> Ich wollte zwei Abfragen und Ausgaben in assembler abfragen und wollte
> den Unterschied zum C Compiler messen.

Sowas muss man nicht "messen", sonder man schaut sich den generierten
Assemblercode an und zählt die Takte.  Die stehen im Manual drin.

Parallel schreibst du dir deine Assemblerimplementierung auf und
zählst dort.

Ansonsten gilt: Never start optimizing before you have profiled it.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jörg Wunsch schrieb:
> AndyS schrieb:
>> Ich wollte zwei Abfragen und Ausgaben in assembler abfragen und wollte
>> den Unterschied zum C Compiler messen.
>
> Sowas muss man nicht "messen", sonder man schaut sich den generierten
> Assemblercode an und zählt die Takte.  Die stehen im Manual drin.
>
> Parallel schreibst du dir deine Assemblerimplementierung auf und
> zählst dort.
>
> Ansonsten gilt: Never start optimizing before you have profiled it.

Gerade zum Profilen sind solche Sequenzen aber nützlich.
Hier hausbacken:
1
#ifndef TICKS_H
2
#define TICKS_H
3
4
#include <avr/io.h>
5
6
#ifdef __ASSEMBLER__
7
.macro ticks n
8
    lds 0, TCNT1
9
    sts tick\n, 0
10
    lds 0, TCNT1+1
11
    sts tick\n+1, 0
12
.endm
13
14
#else /* !__ASSEMBLER__ */
15
16
extern uint16_t volatile tick1;
17
extern uint16_t volatile tick2;
18
19
static inline __attribute__((always_inline))
20
void ticks (char n)
21
{
22
    if (n == 1) cli();
23
    __asm volatile (
24
        "lds __tmp_reg__, %1"       "\n\t"
25
        "sts tick%0, __tmp_reg__"   "\n\t"
26
        "lds __tmp_reg__, %1+1"     "\n\t"
27
        "sts tick%0+1, __tmp_reg__"
28
        :: "n" (n), "n" (&TCNT1)
29
        : "memory");
30
    if (n == 2) sei();
31
}
32
33
#endif /* __ASSEMBLER__ */
34
#endif /* TICKS_H */
Und natürlich muss man aufpassen, was man misst. Die Sequenz braucht 
selber schon 8 Ticks. Und wenn man mit volatile-Variablen unterwegs ist, 
brauchen deren Zugriffe u.U deutlich länger als das, was man eigentlich 
messen will ;-)

Obige Sequenz kommt zB zum Einsatz bei Performance-Test für neue 
libgcc-Algorithmen.

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.