Forum: Compiler & IDEs avr-gcc inline assembler


von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Ich mach grad meine ersten zögerlichen Gehversuche mit inline assembler, 
und hätte meine erste Funktion fertig. Ich hab mir fmuls ausgesucht, 
weil ich das in weiterer Folge auch brauchen werde, wohl wissend dass es 
das schon fertig als builtin gibt. Mir gehts aber ums Lernen und 
Verstehen.

Also: ist das soweit korrekt?
1
static inline int16_t fmul8x8(const int8_t op1, const int8_t op2)
2
{
3
    int16_t result;
4
    asm volatile (
5
        "fmuls %1, %2 \n\t"
6
        "movw  %0, r0  \n\t"
7
        "clr   r1      \n\t"
8
        : "=&r" (result)
9
        : "a" (op1), "a" (op2)
10
        : "r0", "r1"
11
        );
12
    return result;
13
}

Meine Fragen dazu:

a) das Ergebnis von fmul (wie auch von vielen weiteren Operationen) 
steht oft in r0 (oder r0/r1). Das muss ich immer in ein Register 
weiterschieben, oder? Ich kann dem GCC nicht sagen "Ergebnis wäre in r0 
und r1, wenn du magst kannst du dort damit weiterrechnen, sonst schiebs 
dir wohin es dir beliebt"?

b) das "=&r" passt so?

c) müssen/sollen r0 und r1 in der clobber-list angegeben werden?

Danke!

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


Lesenswert?

Michael Reinelt schrieb:

> a) das Ergebnis von fmul (wie auch von vielen weiteren Operationen)
> steht oft in r0 (oder r0/r1). Das muss ich immer in ein Register
> weiterschieben, oder?

Ja, denn r0 und r1 sind für den GCC (leider, die Entscheidung war
vor der Einführung der Multiplikationsbefehle so getroffen worden)
Register mit Sonderfunktionen (generic scratch register, zero register).

> b) das "=&r" passt so?

Ja.

> c) müssen/sollen r0 und r1 in der clobber-list angegeben werden?

Ja, wobei es sogar sein kann, dass du dich selbst drum kümmern musst,
die Konstante 0 wieder nach r1 zu schreiben.  Musst du mal im
generierten Assemblercode schauen, ob die Deklaration als clobber
genügt, dass das der Compiler von sich aus macht.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Jörg Wunsch schrieb:
> Michael Reinelt schrieb:
>
>> a) das Ergebnis von fmul (wie auch von vielen weiteren Operationen)
>> steht oft in r0 (oder r0/r1). Das muss ich immer in ein Register
>> weiterschieben, oder?
>
> Ja, denn r0 und r1 sind für den GCC (leider, die Entscheidung war
> vor der Einführung der Multiplikationsbefehle so getroffen worden)
> Register mit Sonderfunktionen (generic scratch register, zero register).

Danke. ist nciht so tragisch, wenn ich schon unbedingt da ein paar 
nanosekunden einsparen will, mach ich halt das "Weiterrechnen" auch im 
inline-asm.

>> b) das "=&r" passt so?
>
> Ja.
Puh!

Irgendwie hab ich manchmal geselen, dass man "l" (für "lvalue) nehmen 
soll... da blick ich noch nicht so richtig durch...

>> c) müssen/sollen r0 und r1 in der clobber-list angegeben werden?
>
> Ja, wobei es sogar sein kann, dass du dich selbst drum kümmern musst,
> die Konstante 0 wieder nach r1 zu schreiben.  Musst du mal im
> generierten Assemblercode schauen, ob die Deklaration als clobber
> genügt, dass das der Compiler von sich aus macht.

Ich muss es auf jeden fall wieder leeren / auf null setzen. In 
irgendeinem der Cookbooks steht, dass es eben nix bringt r1 in die 
Clobber-Liste mit aufzunehmen, man muss es auf jeden Fall mit clr 
löschen. Die Frage ist eher, ob man üblicherweise r0 und r1 da 
hinschreibt oder nicht.

Aber wenn das soweit mal passt, dann werd ich mich mal an eine fmul_8x16 
rantrauen. Wobei die Kombination inline-asm und fractional multiply 
momentan recht schwerer tobak für mich ist :-)

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


Lesenswert?

Michael Reinelt schrieb:
> Die Frage ist eher, ob man üblicherweise r0 und r1 da hinschreibt oder
> nicht.

Hat zumindest dann Kommentarwert, wenn du das in fünf Jahren mal
wieder liest. ;-)

von Peter D. (peda)


Lesenswert?

Beim GCC inline Assembler kriegt ich immer rote Pusteln im Gesicht und 
Schaum vorm Mund.

Einfacher ist es, ein Object komplett in Assembler zu schreiben (*.S 
Datei). Die Lesbarkeit steigt enorm.

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


Lesenswert?

Peter Dannegger schrieb:
> Beim GCC inline Assembler kriegt ich immer rote Pusteln im Gesicht und
> Schaum vorm Mund.

Nur, weil du nicht verstanden hast, wie genial das Ding ist. ;-)

Die Ernüchterung ist mir gekommen, als ich mir mal angesehen habe,
wie primitiv inline assembly beim IAR gemacht ist.  Da kannst du so
ziemlich alles erraten, was der Compiler wohl ringsum anstellen wird,
und es könnte natürlich schon in der nächsten Version des Compilers
auch ganz anders kommen.  Eigentlich kannst du dich dort bestenfalls
auf globale Variablen verlassen, sonst nichts.

Der GCC dagegen gestattet es, dass der Programmierer selbst bei so
einem tiefen Eingriff wie dem Inline-Assembler dem Compiler noch so
komplett wie möglich die Integration in seine Optimierungsstragie
überlassen kann.  Du beschreibst also nicht: „Ich will mit Register 6
jetzt den Wert verarbeiten, dann den von Register 19 dazu.“, sondern
du beschreibst: „Ich brauche ein beliebiges allgemeines Register, und
dann noch eins aus der oberen Registerhälfte.“  Damit ist es möglich,
dass der Programmierer nicht dem Compiler dumm im Weg rumsteht mit
der Wahl der Register für seinen Assemblercode.

Michael, an deiner Stelle würde ich allerdings den Argumenten Namen
geben, statt sie nur durchzunummerieren.  Das ist ein neueres Feature
des GCC, verbessert aber meiner Meinung nach die Lesbarkeit.  Also
etwa so:
1
static inline int16_t fmul8x8(const int8_t op1, const int8_t op2)
2
{
3
    int16_t result;
4
    asm volatile (
5
        "fmuls %[op1], %[op2] \n\t"
6
        "movw  %[result], r0  \n\t"
7
        "clr   r1      \n\t"
8
        : [result] "=&r" (result)
9
        : [op1] "a" (op1), [op2] "a" (op2)
10
        : "r0", "r1"
11
        );
12
    return result;
13
}

p.s.: Ich denke, das „volatile“ ist hier unnütz.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Jörg, deine Wünsche sind mir Befehl :-)
1
static inline int32_t fmul_16x16(const int16_t op1, const int16_t op2)
2
{
3
    int32_t result;
4
    int8_t zero = 0;
5
    asm (
6
        "fmuls %A[op1], %A[op2]   \n\t"
7
        "movw %A[result], r0     \n\t"
8
        "fmul %B[op1], %B[op2]    \n\t"
9
        "adc %B[result], %[zero]  \n\t"
10
        "movw %C[result], r0     \n\t"
11
        "fmulsu %A[op1], %B[op2]  \n\t"
12
        "sbc %A[result], %[zero]  \n\t"
13
        "add %C[result], r0       \n\t"
14
        "adc %B[result], r1       \n\t"
15
        "adc %A[result], %[zero]  \n\t"
16
        "fmulsu %B[op1], %A[op2]  \n\t"
17
        "sbc %A[result], %[zero]  \n\t"
18
        "add %C[result], r0       \n\t"
19
        "adc %B[result], r1       \n\t"
20
        "adc %A[result], %[zero]  \n\t"
21
        "clr r1                   \n\t"
22
         : [result] "=&r" (result)
23
        : [op1] "a" (op1), [op2] "a" (op2), [zero] "r" (zero)
24
        : "r0", "r1"
25
        );
26
    return result;
27
}

Den Algorithmus hab ich aus der Atmel-Doku  abgeschrieben, keine Angst 
:-) von alleine wär ich nie drauf gekommen, dass da plötzlich ein sbc 
vorkommen muss....

ABER: hier bin ich jetzt sehr verunsichert, welche "Kenner" ich in die 
Parameterlisten schreiben muss. es sind ja diesmal 2- bzw. 
4-byte-Werte...

Jörg? Hilfe!

@Peter: Hier sieht man sehr schön die von Jörg angesprochene Genialität: 
ich brauch ein Register mit Inhalt 0. Das Standard-r1 kann ich nicht 
nehmen, weil da mein Ergebnis der Multiplikation drinnensteckt. ich muss 
mich jetzt nicht entscheiden, sondern kann es dem Compiler überlassen, 
irgendein Register mit 0 zu belegen und im Assembler-Code zu verwenden.

In einem geb ich dir aber recht: Schön ist anders :-)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Michael Reinelt schrieb:
> Ich mach grad meine ersten zögerlichen Gehversuche mit inline assembler,
> und hätte meine erste Funktion fertig. Ich hab mir fmuls ausgesucht,

1
1
#include <stdint.h>
2
3
int16_t fmuls (int8_t a, int8_t b)
4
{
5
    return __builtin_avr_fmuls (a, b);
6
}

Siehe: http://gcc.gnu.org/onlinedocs/gcc/AVR-Built-in-Functions.html


2
1
#include <stdfix.h>
2
3
short fract fmuls_8 (short fract a, short fract b)
4
{
5
    return a * b;
6
}
7
8
// oder
9
10
fract fmuls_16 (short fract a, short fract b)
11
{
12
    return (fract) a * b;
13
}


Wobei 2) etwas ineffizienter ist als 1), aber sicherstellt, dass -1 * -1 
nicht überläuft sonder knapp 1 liefert.

O.g. Funktionen implementiert man bevorzugt als "static inline".

Jörg Wunsch schrieb:
> p.s.: Ich denke, das „volatile“ ist hier unnütz.

Ja.  Ebenso das early-clobber: "=&r" --> "=r", falls du noch weniger:

> dem Compiler dumm im Weg rumsteht[en willst]

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Hallo Johann,

ich hatte gehofft dass du hier reinschaust :-)

Johann L. schrieb:
>     return __builtin_avr_fmuls (a, b);

Danke, kannte ich, aber ich wollte nicht nur fmuls implementieren, 
sondern etwas über inline asm lernen.

> *2*[c]#include <stdfix.h>

Hmmm... hab ich nicht.
1
dpkg-query -l | grep avr
2
ii  avr-libc  1:1.8.0-4.1 
3
ii  gcc-avr   1:4.8-2
die nächsthöhere Version (1.8.0+Atmel-3.4.4-1) hab ich bewusst nicht 
installiert wegen dem lästigen Bug (ähhh was war das gleich? Irgendwas 
mit ISR misspelled)

> Ja.  Ebenso das early-clobber: "=&r" --> "=r"
Warum? "Register should be used for output only" warum ist das 
kontraproduktiv?

Und ich würde mich sehr freuen, wenn mir noch jemand bei meinem letzten 
Beispiel "fmul_16x16" auf die Sprünge helfen könnte, was ich da bei den 
input und output operands hinschreiben soll, wenn mehr als ein Register 
betroffen ist (sprich: 16 oder 32 bits angesprochen werden)

Danke!

von Markus F. (mfro)


Lesenswert?

Michael Reinelt schrieb:
>> Ja.  Ebenso das early-clobber: "=&r" --> "=r"
> Warum? "Register should be used for output only" warum ist das
> kontraproduktiv?

Das & ("early-clobber") sagt dem Compiler, daß dieser output-Operand 
beschrieben ("erschlagen") wird, bevor die inputs "aufgebraucht" sind - 
d.h. daß er für diesen Output keins der Input-Register verwenden darf.

Läßt man das "&" weg (Assembler-Code genau anschauen, ob o.g. Bedingung 
auch wirklich falsch ist), kommt man mit einem Register weniger aus.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Michael Reinelt schrieb:
> Johann L. schrieb:
>> *2*[c]#include <stdfix.h>
>
> Hmmm... hab ich nicht.
>
1
> ii  gcc-avr   1:4.8-2
2
>
> die nächsthöhere Version (1.8.0+Atmel-3.4.4-1) hab ich bewusst nicht

Wenn in "4.8" auch avr-gcc 4.8 drinne ist, dann ist auch Fixed-Point 
Support drinne:

http://gcc.gnu.org/gcc-4.8/changes.html#avr

Allerdings gibt es keine dedizierte Option für ISO/IEC DTR 18037 aka. 
"Embedded-C".  GNU-C muss aktive sein.

>> Ja.  Ebenso das early-clobber: "=&r" --> "=r"
> Warum? "Register should be used for output only" warum ist das
> kontraproduktiv?

Zum einen gibt es bei deinem asm keinen Grund, warum sich Ein- und 
Ausgabe nicht überlappen dürfen.  Zum anderen ist der Registerallokator 
mit "&" konservativer, d.h. man kann beobachten, dass mit "&" u.U. 
unnötige MOVs erzeugt werden, weil die Register-Allokation suboptimal 
arbeitet.  Aufgefallen ist mit das mit Reload und mit IRA, ob LRA diese 
Schwäche immer noch hat, weiß ich net.

Versuch einfach mal Code, in dem ein Wert Eingabe und Ausgabe eines asm 
ist und nach R24 allokiert werden sollte, etwa weil er Parameter / 
Returnwert einer Funktion ist.

> Und ich würde mich sehr freuen, wenn mir noch jemand bei meinem letzten
> Beispiel "fmul_16x16" auf die Sprünge helfen könnte, was ich da bei den
> input und output operands hinschreiben soll, wenn mehr als ein Register
> betroffen ist (sprich: 16 oder 32 bits angesprochen werden)

Genau wie sonst auch.  GCC kennt die Typen der Operanden und allokiert 
entsprechend viele Register.  Kann allerdings Probleme geben, wenn es in 
der gewünschten Registerklasse eng wird, siehe 
http://gcc.gnu.org/PR56479

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Johann L. schrieb:
> Wenn in "4.8" auch avr-gcc 4.8 drinne ist, dann ist auch Fixed-Point
> Support drinne:

Fixed-Point Support dürfte ich haben, zumindest compiliert er den Typ 
_Fract problemlos.

Allerdings finde ich dann im Compiler-Ergebnis für short _Fract * short 
_Fract noch
1
call __mulhq3

Ich denke da fehlt dann noch ein patch von dir, den ich irgendwie 
gefunden habe, mit dem dann direkt fmuls instructions erzeugt werden 
sollten... (zumindest hab ich was im Google dazu gefunden... scheduled 
for 4.9 oder so..)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?


von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Johann L. schrieb:
> Der Code ist bereits in der 4.8.0 enthalten:
>
> 
http://gcc.gnu.org/viewcvs/gcc/tags/gcc_4_8_0_release/gcc/config/avr/avr-fixed.md?view=markup#l187

Interessant... was sollte dann folgender C-Code produzieren:
1
static inline _Fract xx_fmul_8x8_16(const short _Fract op1, const short _Fract op2)
2
{
3
    return (_Fract) op1 * op2;        
4
}                                                                           
5
6
int main(void)
7
{
8
    volatile int8_t a = 47;
9
    volatile int8_t b = 11;
10
    volatile int16_t y;
11
    
12
    y = xx_fmul_8x8_16(a,b);
13
}
Bei mir macht er:
1
        ldd r18,Y+13     ;  b.6, b
2
        ldd r26,Y+14     ;  a.7, a
3
        lsr r18  ; 
4
        clr r18  ; 
5
        ror r18  ; 
6
        mov r19,r18      ; ,
7
        clr r18  ; 
8
        lsr r26  ; 
9
        clr r26  ; 
10
        ror r26  ; 
11
        mov r27,r26      ; ,
12
        clr r26  ; 
13
        call __mulhq3
14
        lsl r25  ; 
15
        sbc r24,r24      ; 
16
        sbc r25,r25      ; 
17
        std Y+6,r25      ;  y, y.8
18
        std Y+5,r24      ;  y, y.8

Abgesehen davon dass ich eigentlich eine fmuls-instruktion erwartet 
hätte, sind die operationen auf r18 (zuerst ein lsr, dann ein clr, dann 
ein ror?) für mich nicht wirklich nachvollziehbar.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Oje, vielleicht sollte man beim testen die richtigen Datentypen 
verwenden...

also nochmal:
1
static inline _Fract xx_fmul_8x8_16(const short _Fract op1, const short _Fract op2)
2
{
3
    return (_Fract) op1 * op2; 
4
}
5
6
int main(void)
7
{
8
    volatile short _Fract a = 0.47;
9
    volatile short _Fract b = 0.11;
10
11
    volatile _Fract y;
12
    
13
    y = xx_fmul_8x8_16(a,b);
14
}

wird zu:
1
  ldi r24,lo8(60)   ;  tmp48,
2
  std Y+4,r24   ;  a, tmp48
3
  ldi r24,lo8(14)   ;  tmp49,
4
  std Y+3,r24   ;  b, tmp49
5
  ldd r18,Y+3   ;  b.0, b
6
  ldd r26,Y+4   ;  a.1, a
7
  mov r19,r18   ; ,
8
  clr r18   ; 
9
  mov r27,r26   ; ,
10
  clr r26   ; 
11
  call __mulhq3
12
  std Y+2,r25   ;  y, D.1660
13
  std Y+1,r24   ;  y, D.1660
14
  ldi r24,0   ; 
15
  ldi r25,0   ;
nicht mehr ganz so viele unnötige Sachen, trotzdem etwas umständlich: 
Zuerst wird r26 (a) geladen, dann nach r27 geschoben und r26 auf null 
gesetzt (selbes Spiel mit b)

Ich vermute der call __mulhq3 erledigt nicht nur das fmuls sondern auch 
die Korrektur nach überlauf?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Michael Reinelt schrieb:
> Oje, vielleicht sollte man beim testen die richtigen Datentypen
> verwenden...

Yupp.  Die Schnittmenge zwischen "int8_t" und "(short) fract" ist { -1, 
0 }.  Nicht sonderlich ergiebig ;-)

>  volatile [auto];
>
> nicht mehr ganz so viele unnötige Sachen, trotzdem etwas umständlich:

Na wenn's dir um unnötige Sachen oder die Quintessenz geht, dann ist 
volatile eine schlechte Wahl:
 
1
#include <stdint.h>
2
#include <stdfix.h>
3
4
fract
5
fmul_8x8_16 (short fract a, short fract b)
6
{
7
    return (fract) a * b;
8
}
 
liefert mit -Os -S -dp:
 
1
fmul_8x8_16:
2
  clr r18   ;  7  fractqqhq2  [length = 2]
3
  mov r19,r24
4
  mov r27,r22   ;  8  fractqqhq2  [length = 2]
5
  clr r26
6
  call __mulhq3   ;  17  *mulhq3.call  [length = 2]
7
  ret   ;  26  return  [length = 1]

Die Werte weden erst nach fract expandiert (fractqqhq2, von QQmode nach 
HQmode), und auf diesen wird dann die Multiplikation erledigt (HQ = HQ * 
HQ).

Bedeutung der GCC machine modes findet man z.B. in Tabelle "Machine 
Modes" des avr-gcc ABI [1] oder — wie immer — in der GCC-Quelle [2][3].

Je nach Kontext, z.B. bei Inlining und -mrelax, verschwinden auch die 
beiden MOVs und das RET.

Register 18/19 und 26/27 werden angefasst, weil __mulhq3 eine 
Nicht-ABI-Funktion ist.  Dies entnimmt man dem mit -dP erzeugten 
Assembler
1
 ; ([...]
2
 ;  (parallel [(set (reg:HQ 24)
3
 ;                  (mult:HQ (reg:HQ 18)
4
 ;                           (reg:HQ 26)))
5
 ;             (clobber (reg:HI 22))])
6
 ; [...])
7
  call __mulhq3   ;  17  *mulhq3.call  [length = 2]
der GCC-Quelle [4] oder der libgcc [5].

Zum Arbeiten mit impliziten Fixed-Point verwendet man die ?bits? 
Funktionen um von einer Welt in die andere zu wechseln.  Mit den Headern 
wie oben:
 
1
static inline int16_t __attribute__((__always_inline__))
2
fmul_i8xi8_i16_inline (int8_t a, int8_t b)
3
{
4
    short fract fa = hrbits (a);
5
    short fract fb = hrbits (b);
6
    return bitsr ((fract) fa * fb);
7
}
8
9
extern int16_t x, y, z;
10
11
void test_fmul_i8xi8_i16 (int8_t a)
12
{
13
    x = fmul_i8xi8_i16_inline (a, a);
14
    y = fmul_i8xi8_i16_inline (a, 64 /* 1/2 */);
15
    z = fmul_i8xi8_i16_inline (64, 64); /* 1/4 */
16
}
 
ergibt:
 
1
test_fmul_i8xi8_i16:
2
  clr r20   ;  6  fractqqhq2  [length = 2]
3
  mov r21,r24
4
  movw r18,r20   ;  7  *movhq/1  [length = 1]
5
  movw r26,r20   ;  8  *movhq/1  [length = 1]
6
  call __mulhq3   ;  9  *mulhq3.call  [length = 2]
7
  sts x+1,r25   ;  11  *movhi/4  [length = 4]
8
  sts x,r24
9
  ldi r26,0   ;  14  *movhq/5  [length = 2]
10
  ldi r27,lo8(64)
11
  call __mulhq3   ;  15  *mulhq3.call  [length = 2]
12
  sts y+1,r25   ;  17  *movhi/4  [length = 4]
13
  sts y,r24
14
  ldi r24,0   ;  18  *movhq/5  [length = 2]
15
  ldi r25,lo8(32)
16
  sts z+1,r25   ;  19  *movhi/4  [length = 4]
17
  sts z,r24
18
  ret   ;  26  return  [length = 1]



[1] http://gcc.gnu.org/wiki/avr-gcc#Calling_Convention
[2] http://gcc.gnu.org/viewcvs/gcc/trunk/gcc/config/avr/avr-modes.def?view=markup
[3] http://gcc.gnu.org/viewcvs/gcc/trunk/gcc/machmode.def?view=markup
[4] http://gcc.gnu.org/viewcvs/gcc/trunk/gcc/config/avr/avr-fixed.md?revision=219188&view=markup#l269
[5] http://gcc.gnu.org/viewcvs/gcc/trunk/libgcc/config/avr/lib1funcs-fixed.S?revision=219188&view=markup#l279

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Johann L. schrieb:
> Yupp.  Die Schnittmenge zwischen "int8_t" und "(short) fract" ist { -1,
> 0 }.  Nicht sonderlich ergiebig ;-)

:-)

Johann L. schrieb:
> Na wenn's dir um unnötige Sachen oder die Quintessenz geht, dann ist
> volatile eine schlechte Wahl:

Gutes Argument. ich hab das volatile nicht hingeschrieben, ich schwöre! 
:-)

Johann L. schrieb:
> Zum Arbeiten mit impliziten Fixed-Point verwendet man die ?bits?
> Funktionen um von einer Welt in die andere zu wechseln.  Mit den Headern
> wie oben:
> [...]
> return bitsr ((fract) fa * fb);

sorry, da bin ich jetzt ausgestiegen: Was genau macht bitsr()? Hier 
versagt auch mein Google...

Ansonsten: Vielen dank für deine ausführlichen Erklärungen. Ich denke in 
diesen _Fract Datenttypen steckt enorm viel Potential, speziell für 
Signalverarbeitung (und allen Bereichen wo man mit [-1..1] arbeitet). 
natürlich kann man das "zu Fuß" nachbauen, aber alleine ein 0.42 * 0.8 
ist mit "händischer fixed-point arithmetik" lästig weil man dauernd 
rechtsscheiben muss bzw. die LSBs wegwerfen. Da wird der Code mit _Fract 
mit Sicherheit weit einfacher, besser lesbar, und effizienter

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Michael Reinelt schrieb:
> Johann L. schrieb:
>> Zum Arbeiten mit impliziten Fixed-Point verwendet man die ?bits?
>> Funktionen um von einer Welt in die andere zu wechseln.  Mit den Headern
>> wie oben:
>> [...]
>> return bitsr ((fract) fa * fb);
>
> sorry, da bin ich jetzt ausgestiegen: Was genau macht bitsr()? Hier
> versagt auch mein Google...

Die Spezifikation steht nicht in Google sondern in IEC TR 18037. 
Letzterer legt nicht nur neue Built-in Macros fest, sondern auch einen 
Zoo neuer Typen und Funktionen.  Die ?bits? Funktionen wandeln 
Ganzzahl-Typen in Fixed-Typen um und umgekehrt, ohne dabei die 
Darstellung zu ändern.

Um zwischen Integer und float zu wechseln kann man z.B. direkt zuweisen 
— was den Wert (soweit möglich) nicht ändert — oder die Bitdarstellung 
ungeändert übernehmen.  Bei float/integer gibt es dafür keine 
Standard-Funktion.  Der standardkonforme (und i.d.R effizienteste) Weg 
wäre da ein memcpy.  Für Fixed gibt es da dedizierte Funktionen für.

Beispiel für accum.  accum hat Suffix "k", analog wie "f" zu float 
gehört.  Damit macht bitsk ein Bitbang von accum zu int_k_t, d.h. zu 
einem integralen Typ mit der gleichen Anzahl Bits und gleicher 
Signedness, ohne jedoch ein einziges Bit der (Binär)darstellung zu 
ändern.  Analog kbits: Integer (int_k_t) rein, k (accum) raus:
 
1
int_k_t /* "long int" bzw. "long long int" mit -mint8 */
2
get_bitsk (void)
3
{
4
    return bitsk (3e-3k) + bitsk (-0x3p-3k);
5
}
 
Die erste Konstante ist 3E-3 als accum, d.h. 3·10^{-3} = 0.003.  Die 
zweite Konstante ist -0x3·2^{-3} = -3/8 = -0.375.

Bitbang nach int_k_t (long im üblichen Kontext) ergibt für die 
Konstanten den jeweils 2^15-fachen Wert weil accum Signatur s16.15 hat 
und long s31.  Wert 1 wird also zu 98 (ca. 98.304) und Wert 2 zu -12288. 
Deren Summe wird dann zurückgegeben.  Das Ergebnis ist also -12190L; als 
accum wären das ca. -0.37201.  Der Fehler von 0.00001 (ca. 0.3 LSBs) 
ergibt sich dadurch, dass 3/1000 nicht exakt als accum darstellbar ist.

Um einen Überblick zu bekommen, was es so an zusätzlichem Werkzeug gibt, 
kann man stdfix.h und dessen Includes lesen oder einen Blick in TR 18037 
riskieren, dort dann insbesondere in Abschnitt 7.18a.

> Ich denke in diesen _Fract Datenttypen steckt enorm viel Potential,
> speziell für Signalverarbeitung (und allen Bereichen wo man
> mit [-1..1] arbeitet).

Mit [-1,1).  Für größere Bereiche dann eben einen der accums, oder 
unsigned fract / accum wenn nicht-negative Werte ausreichen.

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.