d16(An) の記述方法

bitswap (データバスのビットを不規則に交換する)をソフトでやらせると必然的に命令が大量のものになり、10MHz の CPU にやらせるのは現実的ではないと判断した。そこでハードウェアで代替処理をやらせる。

	moveq #1,%d1
	and.l %d2,%d1
	moveq #15,%d0
	lsl.l %d0,%d1
	move.w %d2,%d0
	moveq #14,%d5
	lsr.w %d5,%d0
	moveq #1,%d5
	and.l %d5,%d0
	move.b #14,%d5
	lsl.l %d5,%d0
	or.w %d0,%d1
	move.w %d2,%d0
	move.b #13,%d5
	lsr.w %d5,%d0
	moveq #1,%d5
	and.l %d5,%d0
	move.b #13,%d5
	lsl.l %d5,%d0
	or.w %d0,%d1
	move.w %d2,%d0
	move.b #12,%d5
	lsr.w %d5,%d0
	moveq #1,%d5
	and.l %d5,%d0
	(こんな命令が1回のbitswapで40行以上つづくので省略)

ハード仕様

bitswap のやり方が16個あり、やり方をアドレスで選択する。データバスは8bitとする(理由は74LVX4245が高いから)。 レジスタは16bitなので、どのアドレスでもデータは同じ。

 #  |write                 |read
 -----------------------------------------
 +01|bitswap set0 bit15:8  |bitswapped data bit15:8
 +03|bitswap set0 bit7:0   |bitswapped data bit7:0
    |         :            |  :
    |         :            |  :
 +1d|bitswap set15 bit15:8 |mirror
 +1f|bitswap set15 bit7:0  |mirror

どのアドレスに書いても同じで、読み込みでの結果を替える方式では 5LE 多かったのでこちらにする。あまりかわらないけど。

実装

まずは無難にする。

static inline uint16_t hardswap(volatile uint8_t *p, int offset, uint16_t val)
{
	p[offset] = val >> 8;
	p[offset+2] = val & 0xff;
	return (p[offset] << 8) | p[offset+2];
}

結果。write は %a1 レジスタを介してるのに、 read は abs.l を使うし、8回シフトが無駄。

	move.w %d1,%d0
	lsr.w #8,%d0
	move.b %d0,2(%a1)
	move.b %d1,4(%a1)
	move.b 65538,%d0
	lsl.l #8,%d0
	and.l #65280,%d0
	move.b 65540,%d1
	and.w #0xFF,%d1
	or.w %d0,%d1


インラインアセンブラで movep を使う。movep のアドレッシングモードは dn,d16(An) か d16(An),dn だけなのが曲者。さらにここはループで何度も呼ばれる場所で、ボトルネックの改善に movep を使うわけであって的確な記述が求められる。

static inline uint16_t hardswap(volatile uint8_t *p, int offset, uint16_t val)
{
	p += offset;
	asm(
		"movep.w %0,%1@(1)\n\t"
		"movep.w %1@(1),%d0"
		:"+d"(val) : "a"(p) : "%cc"
	);
	return val;
}

結果。シフト演算は消えたが、ポインタの初期化を関数の度にやっていて無駄。

	move.l #65541,%a0
	move.w %d1,%d0
#APP
	movep.w %d0,%a0@(1)
	movep.w %a0@(1),%d0
#NO_APP
	move.w %d0,%d1

次に、offset を動的に貼りたいのでなんとかしたいのだがうまくいかない。 offset 扱いで貼りたいが、変数を渡しているのでエラーになる。

static inline uint16_t hardswap(volatile uint8_t *p, int offset, uint16_t val)
{
	asm(
		"movep.w %0,%1@(%2)\n\t"
		"movep.w %1@(%2),%d0"
		:"+d"(val) : "a"(p), "J"(offset) : "%cc"
	);
	return val;
}

仕方ないのでマクロにするとコンパイルは通るが、immidiate なのか # が付いて使い物にならない。

#define hardswap(vp, offset, vval) \
	asm ( \
		"movep.w %0,%1@(%2)\n\t" \
		"movep.w %1@(%2),%0" \
		:"+d"(vval) : "a"(vp), "J"(offset) : "%cc" \
	)
#APP
	movep.w %d1,%a0@(#2)
	movep.w %a0@(#2),%d1
#NO_APP

諦めかけたが、インラインアセンブラレジスタ割り当て機能を使わないことにした。つまり文字列を分断してマクロの文字列化を使用する。

 #define hardswap(vp, offset, vval) \
	asm ( \
		"movep.w %0,%1@(" #offset ")\n\t" \
		"movep.w %1@(" #offset "),%0" \
		:"+d"(vval) : "a"(vp) : "%cc" \
	)
#APP
	movep.w %d1,%a0@(12)
	movep.w %a0@(12),%d1
#NO_APP

C で # をつけると、アセンブラの # が消えるなんてナンセンス(これが書きたかっただけ)。ここまでやれば処理は相当なスピードアップが期待でき、16bitバスはいらないと思う。