pic32 inline assembler mplab
von oh'09 www.ultrafex.de

1.	der inline assembler
das asm schlüsselwort
syntax:
	asm ( asm anweisung : ausgabeliste: eingabeliste : clobberliste );

bespiel:
	asm( "lw   %0,%1(%2)" : "=r"(a) : "I"(8), "r"(ptr) );

asm anweisung
" lw   %0,%1(%2)" wie man sieht kann man kann man die register durch platzhalter ersetzen. dazu gleich mehr. rot gibt den ausgabeteil an blau den eingabeteil.

ausgabeliste und eingabeliste
die aus- und eingabeliste dient der zuordnung von variablen/konstanten innerhalb der asm anweisung. sie besteht aus einem oder mehreren constrains in dem beispiel „=r“, „r“ und „I“.  dem ausgabe constrain wird immer ein = vorangestellt. der constrain gibt den platzhaltertypen an.
in dem beispiel „=r“(a) bedeutet das das ergebnis der variable a zugewiesen wird.

mehrere ein- oder ausgaben werden durch komma getrennt wie man oben rechts sieht.

platzhalter
die platzhalter beginnen mit einem % mit anschließender platzhalternummer. die platzhalter referenzieren teile aus der aus- und eingabenliste. wenn register direkt verwendet werden müssen diese mit $register angegeben werden.

beispiel für die platzhalternummerierung:

	asm( "lw   %0,0(%1)" : "=r"(a): "r"(ptr) );

%0 wird durch "=r"(a)und %1 wird durch "r"(ptr). wie man sieht werden die platzhalter von 0 beginnend hochgezähl. wenn es keine ausgabeliste gibt sieht das so aus:
	
asm( "lw   $t0,0(%0)" : : "r"(ptr) );

constrains
constrains geben den platzhaltertypen an
	„r“ oder „=r“ zugriff auf eine variable
	„I“ gibt eine konstante an

mag sein das es noch mehr gibt. andere hab ich bisher nicht benutzt.

die eingabe-, ausgabe- und clobberliste sind optional.  wenn sie nicht verwendet werden können diese auch weggelassen:

	asm( "nop" );

wird z.b. nur die clobberliste verwendet wird die ein- und ausgabeliste  leer gelassen.

asm( "addu $t0,$t0,#1" : : : "t0" );

clobberliste
die clobberliste wird dazu benötigt um dem compiler mitzuteilen welche cpu register verändert werden. 

asm( "addu $t0,$t0,#1" : : : "t0" );

wenn man direkt mit c variablen arbeitet braucht man die clobberliste nicht sofern man es ordentlich macht. wenn man z.b. register während einer berechnung temporär braucht müssen diese in der clobberliste angegeben werden. sonst wird ggf. das register vom compiler vergeben und durch die asm anweisung zerstört. ohne funktioniert es wenn man ein register erwischt was nicht benutzt wird. wird die funktion komplizierter gibt’s irgendwann ärger. also immer drauf achten.
register in der clobberliste sind immer ohne $ !

mehrzeilige asm anweisungen 
sind möglich und müssen mit einem \n\t am ende des string abgeschlossen werden.

	asm( „nop \n\t“
                       „nop“ );

wie man sieht kann man die asm anweisung beliebig oft unterbrechen. die asm anweisung kann auch nach dem befehl unterbrochen werden und mit z.b. defines vervollständigt werden.

damit kann man dann ganz interessante sachen machen wie:

#define load( a ) asm volatile( "lw " "$"reg ",%0($t0)" : : "I"(a) : reg );	
main()
{

#define reg "a0"
load( 2 );

#undef reg
#define reg "a1"
load( 3 );
}

das macro load gibt je nach dem wie reg definiert ist das ergebnis in a0 oder a1 zurück. wozu brauch man das ? z.b. wenn man am optimieren ist und den gleichen codeanschnitt für unterschiedliche register braucht. somit hat man eine codebasis aber kann sie für unterschiedliche register einsetzen.

labels
labels können innerhalb des inline assemblers definiert werden.
z.b.
asm( "beq $t0, $t1, label" );
asm( "nop" );
++a;
asm( "nop" );
asm( "nop" );
asm( "label: nop" );

wie man sieht muss das label nicht unbedingt in einer asm anweisung liegen. es können zwischendurch auch c statements eingefügt werden.

der inline assembler und der optimizier
früher hab ich immer mit dem optimizer gekämpft da er asm statements einfach nicht übernommen hat. das war einfach unwissenheit weil ich auch mit der syntax des inline assemblers nicht vertraut war. 

ein beispiel:
register int a asm( "t0" );
register int b asm( "t1" ) = 4567;
register int *ptr asm( "a3" ) = (int*)0xa0000000;

asm( "lw   %0,0(%1)" : "=r"(a) : "r"(ptr) );
asm( "lw   %0,4(%1)" : "=r"(b) : "r"(ptr) );
asm( "addu $t0,$t0,$t1" );
asm( "nop" );
*ptr = a;
asm( "nop" );

erzeugter code ist:
33:                  register int a asm( "t0" );
34:                  register int b asm( "t1" ) = 4567;
35:                  register int *ptr asm( "a3" ) = (int*)0xa0000000;
9D00CF2C  3C07A000   lui         a3,0xa000
36:                  
37:                  asm( "lw   %0,0(%1)" : "=r"(a) : "r"(ptr) );
9D00CF30  8CE80000   lw          t0,0(a3)
38:                  asm( "lw   %0,4(%1)" : "=r"(b) : "r"(ptr) );
39:                  asm( "addu $t0,$t0,$t1" );
9D00CF34  01094021   addu        t0,t0,t1
40:                  asm( "nop" );
9D00CF38  00000000   nop         
41:                  *ptr = a;
9D00CF3C  ACE80000   sw          t0,0(a3)
42:                  asm( "nop" );

wie man sieht fehlt die anweisung
asm( "lw   %0,4(%1)" : "=r"(b) : "r"(ptr) );

warum ist das so ?

das liegt daran das optimizer prüft welche variablen verwendet werden. d.h. variable b wird in der anweisung:
asm( "lw   %0,4(%1)" : "=r"(b) : "r"(ptr) );

zwar zugewiesen. es wird aber aus compiler sicht nichts weiter mit gemacht. der compiler erkennt den zusammenhang zwischen den lw statement und dem addu nicht. dazu müsste er die addu anweisung interpretieren und dies tut er nicht.

die zuweisung *ptr = a; habe ich gemacht damit nicht alles wieder wegoptimiert wird weil a ja nicht weiter verwendet wird somit redundant ist.

richtig ist es:
register int a asm( "t0" );
register int b asm( "t1" ) = 4567;
register int *ptr asm( "a3" ) = (int*)0xa0000000;

asm( "lw   %0,0(%1)" : "=r"(a) : "r"(ptr) );
asm( "lw   %0,4(%1)" : "=r"(b) : "r"(ptr) );
asm( "addu %0,%1,%2" : "=r"(a) : "r"(a), "r"(b) );
asm( "nop" );
*ptr = a;
asm( "nop" );

erzeugter code ist:
28:                  register int a asm( "t0" ) = 1234;
29:                  register int b asm( "t1" ) = 4567;
30:                  register int *ptr asm( "a3" ) = 0xa0000000;
9D00CF24  3C07A000   lui         a3,0xa000
31:                  
32:                  asm( "lw   %0,0(%1)" : "=r"(a) : "r"(ptr) );
9D00CF28  8CE80000   lw          t0,0(a3)
33:                  asm( "lw   %0,4(%1)" : "=r"(b) : "r"(ptr) );
9D00CF2C  8CE90004   lw          t1,4(a3)
34:                  asm( "addu $t0,%0,%1" : : "r"(a), "r"(b) );
9D00CF30  01094021   addu        t0,t0,t1
35:                  asm( "nop" );

wie man sieht die zeile:
asm( "addu $t0,%0,%1" : : "r"(a), "r"(b) );

wurde geändert um dem compiler zu signalisieren das b in dieser anweisung verwendet wird.

was lernt man daraus? wenn der compiler inline asm statements weglässt hat das meistens auch einen grund den man nicht ignorieren sollte (und nicht per brecheisen volatile beheben). das ist eigentlich ein zeichen dafür das irgendwas nicht ganz sauber ist bzw. etwas ohne das wissen des compilers gemacht wird oder der teil redundant ist. 
wenn z.b. die zeile *ptr = a; nicht wäre würde der compiler würde der compiler alles als redundant erkennen. stimmt ja auch ;)

sachen wie:
asm( "addu $t0,$t0,$t1" );

wo direkt auf den register gearbeitet und man sich darauf verlässt das die register so stimmen sollte man vermeiden. dies kann man nur machen wenn man auf zwischenergebnissen arbeitet und diese aber auch in der clobberliste dem compiler mitteilt. tut man das nicht gibt es früher oder später
ärger.

die ausgabe von anweisungen erzwingen
gleiches beispiel wie oben nur leicht modifiziert:
register int a asm( "t0" );
register int b asm( "t1" ) = 4567;
register int *ptr asm( "a3" ) = (int*)0xa0000000;

asm volatile ( "lw   %0,0(%1)" : "=r"(a) : "r"(ptr) );
asm volatile ( "lw   %0,4(%1)" : "=r"(b) : "r"(ptr) );
asm volatile ( "addu $t0,$t0,$t1" );
asm volatile ( "nop" );

erzeugter code ist:
33:                  register int a asm( "t0" );
34:                  register int b asm( "t1" ) = 4567;
35:                  register int *ptr asm( "a3" ) = (int*)0xa0000000;
9D00CF2C  3C07A000   lui         a3,0xa000
36:                  
37:                  asm volatile ( "lw   %0,0(%1)" : "=r"(a) : "r"(ptr) );
9D00CF30  8CE80000   lw          t0,0(a3)
38:                  asm volatile ( "lw   %0,4(%1)" : "=r"(b) : "r"(ptr) );
9D00CF34  8CE90004   lw          t1,4(a3)
39:                  asm volatile ( "addu $t0,$t0,$t1" );
9D00CF38  01094021   addu        t0,t0,t1
40:                  asm volatile ( "nop" );

wie man sieht wurde die anweisung "lw   %0,4(%1)" : "=r"(b) : "r"(ptr)  übernommen obwohl "addu $t0,$t0,$t1" eigentlich nicht „korrekt“ formuliert ist.

volatile nach der asm anweisung bedeutet das der optimizer ausgestellt wird und die ausgabe erzwungen wird. insbesondere wird die anweisung auch nicht durch den optimizer vorschoben z.b. in loops. volatile ist mit vorsicht zu geniessen und sollte nur angewendet werden wenns wirklich nötig ist.

zum schluss
ob man wirklich inline assembler braucht muss man selbst entscheiden. meistens bringen kleine änderung am c-code das der code schnell genug läuft. ich verwende inline assembler wenn nur in innerloops. insbesondere beim rechnen mit mult, madd msub div usw.

wichtig ist jedoch immer zu prüfen was der compiler draus macht d.h. sich das disassembly listing oder memory ansehen. nicht blind drauf vertrauen was man getippt hat.

ein beispiel:
volatile int a;
int b;
int *ptr = (int*)0xa0000000;

asm ( "li   %0,0x1112" : "=r"(a) );
asm ( "li   %0,0x2222" : "=r"(b) );
asm ( "addu %0,%1,%2" : "=r"(a)  : "r"(b),"r"(a) );
asm( "nop" );
*ptr = a;

erzeugter code ist:
59:                  volatile int a;
60:                  int b;
61:                  int *ptr = (int*)0xa0000000;
9D00CF58  3C02A000   lui         v0,0xa000
62:                  
63:                  asm ( "li   %0,0x1112" : "=r"(a) );
9D00CF30  24021112   addiu       v0,zero,4370
9D00CF34  AFA20000   sw          v0,0(sp)
9D00CF38  8FA20000   lw          v0,0(sp)
64:                  asm ( "li   %0,0x2222" : "=r"(b) );
9D00CF3C  24022222   addiu       v0,zero,8738
65:                  asm ( "addu %0,%1,%2" : "=r"(a)  : "r"(b),"r"(a) );
9D00CF40  8FA30000   lw          v1,0(sp)
9D00CF44  00431021   addu        v0,v0,v1
9D00CF48  AFA20000   sw          v0,0(sp)
9D00CF4C  8FA20000   lw          v0,0(sp)
66:                  asm( "nop" );
9D00CF50  00000000   nop         

wie man sieht kommt nicht wirklich das raus was man erwartet. die „intelligenz“ des compilers erweitert den inline assembler code doch deutlich. natürlich liegt das am volatile. klar. dies sollte nur ein verdeutlichen das das was man im inline assembler schreibt nicht unbedingt das ist was nachher im code steht. 

und vorsicht mit dem disassembly fenster. manchmal zeigt es nicht immer alles an und lässt anweisungen aus. oder zeigt totalen mist… an anscheinend ein bug ? ein hinweis darauf ist immer wenn sich die adresse nicht um 4 bytes erhöht sondern grössere sprünge drin sind.

wenn man sich 100% sicher sein will die adresse vom disassembly fenster kopieren und ins memory fenster gehen und da goto.

also vertrauen ist gut kontrolle ist besser.