die blöden Bits wieder...uint16_t zu 4-bit parallel out



  • Hallo liebe Community!

    Ich stehe mal wieder auf dem Schlauch beim Thema Bits und ich hoffe Ihr könnt mir helfen. 😇

    ich habe eine leere Funktion

    void Funktion(uint16_t A,  uint16_t B){
    }
    

    an die ich die Werte A =123 und B = 65535 übergebe und ich habe ein 'buffer'-Array aus 3 'uint32_t'.

    Ziel ist es, den 'buffer' so zu füllen, dass am Ende die Werte A und B an der richtigen Position zu finden sind / übergeben werden.

    Ich hoffe das ist verständlich. 😁

    Hinweis:

    Der 'buffer' ist für einen 4-bit parallel Output gedacht.

    
        //So MUSS der buffer am Ende aussehen (für A= 123 und B= 65535), damit das "Ergebnis" stimmt. 
        //Die Pin-Daten gehen ab (0x5555) los, **16 x 4 bit**, 
        //ergo "0x5555 0x5555 0x5777 0x7577"
    
        uint32_t buffer[3] = 0;
    
        buffer[0] = (0x6117 << 16) | (0x5555);  // 0x6117 ist KONSTANT!
        buffer[1] = (0x5555 << 16) | (0x5777);
        buffer[2] = (0x7577 << 16) | (0x0000);
    
    MUSTER:
    0x             5   5   5   5     5   5   5   5     5   7   7   7     7   5   7   7
                  
    pin4           1   1   1   1     1   1   1   1     1   1   1   1     1   1   1   1  = immer 1
    pin3           0   0   0   0     0   0   0   0     0   1   1   1     1   0   1   1  =  (A-Wert)  = 123
    pin2           1   1   1   1     1   1   1   1     1   1   1   1     1   1   1   1  =  (B-Wert)  = 65535
    pin1           0   0   0   0     0   0   0   0     0   0   0   0     0   0   0   0  = immer 0
    

    Manuell funktioniert das ganze schon einmal mit dem oben gesehen buffer[].

    AAAber.....

    Wie genau muss nun meine Funktion aussehen um die uint16_t Werte (A und B ) in das oben gesehene MUSTER zu übertragen?

    Bitte helft mir ich verzweifel hier noch 🤪

    Gruß Chris


  • Mod

    Wow, das muss man ja 5x lesen, bevor man die Erklärung versteht.

    Ist deine Frage eher, wie man das effizient macht, oder kommst du gar nicht auf den grundlegenden Algorithmus?

    Voraussetzung: Du googelst, wie man gezielt einzelne Bits liest und setzt, falls noch nicht bekannt.

    Grundlegender Algorithmus (Alle Angaben mit 0 als Anfang der Zählung, und ganz rechts steht das 0. Bit):

    • Für N von 0 bis 3 (Das sind unsere Quadranten):
      • Zielwert auf 0001000100010001 setzen (Das 0xx1-Muster kommt von den pin1 und pin4. Die Werte dazwischen habe ich willkürlich 0 gesetzt, wir überschreiben sie sowieso im nächsten Schritt)
      • Für B von 0 bis 3 (Damit adressieren wir die Bits):
        • Bit 1+4*B des Zielwerts auf den Wert des B+4*N'ten Bits von Wert A setzen
        • Bit 2+4*B des Zielwerts auf den Wert des B+4*N'ten Bits von Wert B setzen
      • Der Zielwert ist nun das Resultat für Quadrant N (z.B. 7577 für N=0). Dieses merkt man sich oder fügt es gleich an passender Stelle in den buffer ein.

    Ungetestet. So wie ich mich kenne, ist da bestimmt irgendwo ein Bit um eine Position daneben.

    Das kann man garantiert durch irgendwelche tollen Tricks noch wesentlich beschleunigen. Sei es durch tolle Bitmanipulationstricks, oder durch maschinenabhängige Spezialbefehle. Aber bevor nicht klar ist, ob das überhaupt deine Frage ist, denke ich darüber nicht nach.



  • Wenn ich es richtig verstanden habe, musst du die Bits immer so verschieben, dass jedes vierte von deinem A- bzw. B-Wert gesetzt wird. Bei A und B dann noch 1 bzw. 2 geshiftet. Und immer 1 und immer 0 sollten ja Konstanten sein. Die 4 Werte dann verodern.

    Das Aufspreizen ginge z.B. mit _pdep_u32 (bzw. dem u64-Gegenstück) Intrinsics. (aber nur, wenn ich dich richtig verstanden habe).



  • @SeppJ

    Danke Dir schon einmal für die Antwort!

    Die muss ich jetzt auch erst einmal 5x lesen, da ich blutiger Anfänger bin.

    Kling plausibel auch wenn mir ein klein wenig Code dabei sehr geholfen hätte.

    Mein Gedankengang war folgender:

    • 2 uint64_t variablen erstellen (16x4bits)
    • beide variablen anpassen (1x für A und 1x für B )
    • beide variablen OR

    müsste ja die korrekten bytes liefern, oder?

    -vom Ergebnis des OR mit einer Maske (0xFFFF000000000000) die ersten Bits für den buffer holen...usw


  • Mod

    @wob sagte in die blöden Bits wieder...:

    Das Aufspreizen ginge z.B. mit _pdep_u32 (bzw. dem u64-Gegenstück) Intrinsics. (aber nur, wenn ich dich richtig verstanden habe).

    Klingt von der Beschreibung her richtig. Was mich eher wundert: Da gibt es ein Intrinsic für? Kann mir jemand erklären, wofür das gut ist? Die Operation klingt so exotisch. Was ist denn da die Anwendung, für die es sich lohnt, so etwas in einen Prozessor einzubauen?


  • Mod

    @Hamstaaa sagte in die blöden Bits wieder...:

    Kling plausibel auch wenn mir ein klein wenig Code dabei sehr geholfen hätte

    Später oder morgen, falls sich nicht jemand anderes findet. Jetzt gerade habe ich nicht die Zeit, das auszuprogrammieren.



  • @SeppJ

    Okay Sepp ich versuche das mit der Funktion mal selbst. Auch wenn ich nicht glaube dass das was wird😅

    Einwände / Änderungsvorschläge sind gern gesehen

    void Funktion(uint16_t A,  uint16_t B){
    
    uint32_t buffer[3] = 0;
    
        for (int i=0; i <3; i++){           // Quadrant
    
    
        }
    
    
    
    buffer[0] = (0x6117 << 16) | 0x1111;         
    buffer[1] = ;
    buffer[2] = ;
    
    }
    

    für mehr fehlt mir die Erfahrung 🤮

    @SeppJ sagte in die blöden Bits wieder...uint16_t zu 4-bit parallel out:

    Grundlegender Algorithmus (Alle Angaben mit 0 als Anfang der Zählung, und ganz rechts steht das 0. Bit):

    • Für N von 0 bis 3 (Das sind unsere Quadranten):

      • Zielwert auf 0001000100010001 setzen (Das 0xx1-Muster kommt von den pin1 und pin4. Die Werte dazwischen habe ich willkürlich 0 gesetzt, wir überschreiben sie sowieso im nächsten Schritt)

      • Für B von 0 bis 3 (Damit adressieren wir die Bits):

        • Bit 1+4*B des Zielwerts auf den Wert des B+4*N'ten Bits von Wert A setzen

        • Bit 2+4*B des Zielwerts auf den Wert des B+4*N'ten Bits von Wert B setzen

      • Der Zielwert ist nun das Resultat für Quadrant N (z.B. 7577 für N=0). Dieses merkt man sich oder fügt es gleich an passender Stelle in den buffer ein. Ungetestet. So wie ich mich kenne, ist da bestimmt irgendwo ein Bit um eine Position daneben.

    Eventuell kann ja ein erfahrener User helfen und auf die Schnelle etwas schreiben 🙂



  • @SeppJ sagte in die blöden Bits wieder...uint16_t zu 4-bit parallel out:

    Später oder morgen, falls sich nicht jemand anderes findet. Jetzt gerade habe ich nicht die Zeit, das auszuprogrammieren.

    Ja lieber Sepp, bitte erbarme Dich 🙂



  • Bin zwar nicht @SeppJ , aber hier meine Lösung:

    #include  <immintrin.h>
    #include <cstdint>
    
    int main() {
        const uint64_t mask = 0b1000100010001000100010001000100010001000100010001000100010001;
        
        uint16_t pin4 = 0xFFFF;
        uint16_t pin3 = 123;
        uint16_t pin2 = 0xFFFF;
        uint16_t pin1 = 0;
    
        auto result = 
            _pdep_u64(pin1, mask << 3) |
            _pdep_u64(pin2, mask << 2) |
            _pdep_u64(pin3, mask << 1) |
            _pdep_u64(pin4, mask);
        return result - 0x5555555557777577;
    }
    

    Wie du hier siehst, wird 0 zurückgegeben. Also funktioniert der Code hier so, wie er soll 🙂 Geht so für >= Haswell, also Intel i-Prozessoren ab 4. Generation. Sonst muss man es von Hand machen - dazu kannst du einfach den Code von Intel nehmen: https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_pdep_u64&expand=4152


  • Mod

    @Hamstaaa sagte in die blöden Bits wieder...uint16_t zu 4-bit parallel out:

    @SeppJ sagte in die blöden Bits wieder...uint16_t zu 4-bit parallel out:

    Später oder morgen, falls sich nicht jemand anderes findet. Jetzt gerade habe ich nicht die Zeit, das auszuprogrammieren.

    Ja lieber Sepp, bitte erbarme Dich 🙂

    Morgen. Aber ich bin ein bisschen enttäuscht, denn was ich da beschrieb kann man fast Wort für Wort nach C++ übersetzen. Wenn du keine einfachen Abläufe nachprogrammieren kannst, dann ist Bitmanipulation zu dieser Zeit noch nichts für dich. Oder verstehst du auch nur eine Zeile von dem was wob geschrieben hat? Wenn du Code nur abschreibst, ohne ihn zu verstehen, lernst du nichts.



  • @wob

    Vielen Dank für den Code.

    Vielleicht hätte ich noch erwähnen sollen, dass der Code auf einem ESP32 läuft. 😅

    _pdep_u64 kann ich also leider nicht nutzen ☹

    @SeppJ

    Ja, mit dem Code von Wob kann ich ein bisschen was anfangen.

    nur leider kenne ich die Funktion _pdep_u64 nicht.

    stehe echt auf dem Schlauch. Vielleicht fehlt mir auch die Übung.



  • Schreib das pdep doch 1:1 von der Intel-Beschreibung ab:

    (ungetestet)

    uint64_t my_pdep_u64(uint64_t a, uint64_t mask) {
        auto tmp = a;
        uint64_t dst = 0;
        int m = 0;
        int k = 0;
        const uint64_t one = 1;
        while (m < 64) {
            if (mask & (one << m)) {
                dst |= uint64_t((tmp & (one << k)) != 0) << m;
                k = k + 1;
            }
            m = m + 1;
        }
        return dst;
    }
    


  • @wob

    cool danke Wob!

    jetzt liefert deine vorherige Funktion bei mir auch das Richtige Ergebnis. 🤗

    jetzt werde ich versuchen das 'result' an die Richtige stelle im Buffer zu bringen.

    Vielen Dank schon mal an alle.

    Ihr seid Super hilfsbereit!!


Log in to reply