6

私は現在プロジェクトに取り組んでおり、バイトの順序を逆にする必要があります。現在、AVR Studio Mega32 マイクロコントローラを使用しています。

例えば:

0000 0001 becomes 1000 0000
0001 0110 becomes 0110 1000
1101 1001 becomes 1001 1011

まず、私はこれを持っています:

ldi r20,0b00010110

r20 が 01101000 になるようにバイトを逆にする最も簡単な方法は何ですか?

4

5 に答える 5

3

これはスニペットです - これは GNU ツールチェーン (avr-gcc、binutils、avr-libc など) 用に書かれていますが、簡単に適応できるはずです:

static inline __attribute__ ((always_inline))
uint8_t avr_reverse_byte (uint8_t x)
{
    x = ((x & 0x55) << 1) | ((x & 0xaa) >> 1);
    x = ((x & 0x33) << 2) | ((x & 0xcc) >> 2);

    /* x = ((x & 0x0f) << 4) | ((x & 0xf0) >> 4); */

    __asm__ ("swap %0" : "=r" (x) : "0" (x)); /* swap nibbles. */

    return x;
}

swapそのため、命令で実装された最後の hi-lo ニブル スワップを除いて、「C」コードからの改善はあまりありません。

于 2013-03-17T11:11:02.420 に答える
2

もう1つの簡単な方法は、キャリーフラグを使用することです。

8回繰り返す:

lsl r20 ; shift one bit into the carry flag
ror r0  ; rotate carry flag into result 

(入力r20、出力、破棄されたr0内容r20。レジスタは自由に変更できます。)

これは、2バイトで16命令を使用し、各1サイクル= 32バイトのプログラムメモリであり、完全に「展開」されたときに1バイトを反転するために16サイクルを使用します。ループにラップされると、コードサイズを減らすことができますが、実行時間は長くなります。

于 2013-03-19T09:33:53.563 に答える
2

現在、AVR コードを提供できません。しかし、一般的なビット反転テクニックは次のとおりです。

abcd efgh   p
badc fehg   p = ((p and 0AAh) shr 1) or ((p shl 1) and 0AAh)
dcba hgfe   p = ((p and 033h) shr 2) or ((p shl 2) and 033h)
hgfe dcba   p = ((p and 00Fh) shr 4) or ((p shl 4) and 0F0h)
于 2013-03-15T15:50:01.870 に答える
1

バイトの 2 つの半分の 4 ビット (16 エントリ) ルックアップ テーブルは、適切なトレードオフのように見えます (@Aki がコメントで指摘しているように)。

AVR 命令はそれぞれ 2 バイトであるため、16 バイトのテーブルは 8 命令と同じスペースを消費します。(おそらく、配列を 256 バイト単位でアラインしてインデックス作成を gcc よりもはるかに安価にできる場合を除いて、速度やサイズの点で価値がないことがわかります。)

各バイトの上位半分と下位半分を使用して、LUT をパックすることは可能です。ただし、それは、テーブル サイズ (8 バイト = 4 命令) を節約するよりも、インデックス作成 (インデックスのビット 4 で分岐を使用して、マスクする前に条件付きで SWAP を使用) に多くのコストがかかります。


AVR GCC の機能を比較してみましょう。gcc4.6 では、Brett のコードをコンパイルするときに、驚くほどひどい最適化の失敗があります (実際にintは、結果を uint8_t に切り捨てて、その利点を十分に活用していません)。皮肉なことに、SWAP 命令を使用してローテーションに最適化します。x<<4 | x>>4(AVR には複数カウントのローテート命令がなく、通常のローテートはローテート スルー キャリーです。シフトは命令ごとに 1 つのカウントでのみ使用できます。)

#include <stdint.h>

uint8_t reverse_byte_alu (uint8_t x)
{
    uint8_t xeven = x & 0x55,  xodd = x & 0xaa;
    x = (xeven << 1) | (xodd >> 1);  // swap adjacent bit pairs

    xeven = x & 0x33,  xodd = x & 0xcc;
    x = (xeven << 2) | (xodd >> 2);  // swap adjacent 2-bit chunks

    x = ((x << 4) | (x >> 4));  // 4-bit rotate is recognized as SWAP
    return x;
}

gcc4.6 -O3(Godbolt コンパイラ エクスプローラ)でこの asm にコンパイルします。見逃された最適化は見られません。

reverse_byte_alu:
    mov r25,r24
    andi r25,lo8(85)
    lsl r25
    andi r24,lo8(-86)
    lsr r24
    or r25,r24              # swap of adjacent bits done

    mov r24,r25
    andi r24,lo8(51)
    lsl r24
    lsl r24                 # << 2
    andi r25,lo8(-52)
    lsr r25
    lsr r25                 # >> 2
    or r24,r25              # swap pairs of bits done

    swap r24                # swap nibbles
    ret

16 命令、各 2 バイト、すべて 1 サイクル。(除くret) https://www.microchip.com/webdoc/avrassemblyr/avrassemblyr.wb_instruction_list.html

したがって、これは、を含まない16のシングルサイクル命令を必要とする@JimmyBの回答よりもわずかに優れています。(ただし、小さなループに巻き上げることができます)。ret


AVR では、配列のインデックス作成は安価ではありません。アドレッシング モードの唯一の選択肢は、ポスト インクリメントまたはプリ デクリメントであり、即時置換はありません。したがって、16 ビットの配列アドレスを 4 ビットのニブル値に追加する必要があります。配列がアドレス空間の下位 256 バイトにある場合、これは安価になる可能性があります。または、配列が 256 バイトにアラインされている場合は、ポインタ レジスタの上位バイトを設定し、ルックアップ値を下位バイトに入れることができます。(gcc はこの最適化を見逃しています)。

配列を 16 バイト境界に整列するように gcc に指示すると、アドレス計算が安価になりますが、命令の総数は 18 であり、そのうちのいくつかはマルチサイクル命令です。

__attribute__((aligned(16)))  // makes indexing cheaper
static const uint8_t reverse_nibble[16] = {
        0,      0b1000, 0b0100, 0b1100,
        0b0010, 0b1010, 0b0110, 0b1110,
        0b0001, 0b1001, 0b0101, 0b1101,
        0b0011, 0b1011, 0b0111, 0b1111
        };

uint8_t reverse_byte_nibble_LUT (uint8_t x) {
    uint8_t hi = reverse_nibble[x>>4];
    hi = ((hi << 4) | (hi >> 4));  // SWAP instead of SWAP+AND for just a shift
    uint8_t lo = reverse_nibble[x & 0x0f];
    return hi | lo;
}

17 命令にコンパイルされ、非インクリメント/デクリメント アドレッシング モードで FLASH にアクセスする場合、LD は 2 サイクルの命令です。(フラッシュまたは内部 SRAM にアクセスしていない場合、一部の CPU では 1 サイクルです)。

  # gcc4.6 output, not optimal
    mov r26,r24
    swap r26
    andi r26,lo8(15)
    ldi r27,lo8(0)
    subi r26,lo8(-(reverse_nibble))   # AVR doesn't have add-immediate byte, only sub
    sbci r27,hi8(-(reverse_nibble))   # X register = (x>>4) - (-LUT_base)
    ld r25,X
    swap r25
    mov r30,r24
    ldi r31,lo8(0)
    andi r30,lo8(15)
    andi r31,hi8(15)                 # missed opt, this is a double-redundant 0 & 0
    subi r30,lo8(-(reverse_nibble))
    sbci r31,hi8(-(reverse_nibble))
    ld r24,Z
    or r24,r25
    ret

ldi r27, 0/sbci r27はおそらく最適化の失敗です。16 バイトのアラインされたテーブルでは、上位バイトにキャリーを取得できません。私たちはできると思います:

    # generate r30 = x&0x0f
    subi r30,lo8(-(reverse_nibble))   # ORI would work, too.  no-op with 256-byte aligned table
    ldi  r31,hi8(reverse_nibble)      # reuse this for hi and lo

したがって、これはインデックス作成の改善により速度が向上する可能性がありますが、合計サイズ (コード + テーブル) は明らかに悪くなります。

于 2018-06-30T10:08:13.587 に答える