これまで 32ビット Intel x86 アーキテクチャのサンプルを元に、
FXマイコンとは?
学研から発売されていた電子ブロックという製品をご存知でしょうか?
この電子ブロックのラインナップの中に、
今回はこのFXシリーズの4ビットマイコン
しかし、
また、
シミュレータソフトを使用した場合:
- 持ち運びの手間がない
- プログラム保存/
呼び出しが可能 - 実行中のレジスタ/
メモリ内容を確認可能
といった利便性が得られますが、
FX マイコン向けアセンブラ
4ビットという非常に制約の厳しいアーキテクチャですので、
文献やウェブ上の紹介記事等では、
そこで、
以下の方法で、
- 上記URLのページを表示
- ページ中から
“Download” タブを選択 - “Tags & snapshots”
の “tip” [3] から、 アーカイブファイルの形式 (zip/ gz/ bz2) を選択してクリック - ブラウザの指示に従い、
ファイルに保存 - ダウンロードしたアーカイブを展開
なお、
アーカイブ展開先に格納されているgmc4as.
がアセンブラを実装しているPythonソースです。アセンブルしたいプログラムがsource.
に記述されている場合、
$ python gmc4as.py source.s
00 A | TIY | 1| movw $0x0E, %y
01 E | E | |
02 5 | MA | 2| movw (%y), %a
03 3 | CY | 3| swap
| | | | |
| | | | +---- オリジナルソースの内容
| | | +-------- オリジナルソースでの行番号
| | +--------------- 固有ニーモニックでの表記
| +--------------------- 16進プログラムデータ
+----------------------- 16進アドレス
ソースコード中に文法上の問題がなければ、
gmc4as.
が解釈可能なニーモニック等に関しては、
ちなみに
実装対象の仕様決め
今回は FX マイコンを使用して、
FXマイコン添付のサンプルプログラムや、
とりあえず、
- デスティネーションデータ領域: 0x50 ~ 0x56
- ソースデータ領域: 0x57 ~ 0x5D
上記の各7ワードの領域に、
なお、
繰り上がりに配慮した加算処理
まずは、
変数領域の定義
N桁の多倍長加算処理の骨子をC言語風に記述するならば、
carry = 0;
for(i = N - 1 ; 0 <= i ; i -= 1){
dst[i] += src[i] + carry;
carry = 上記演算での繰り上がりの有無;
}
C言語では演算でのキャリー発生を検出できないため、carry
変数の更新に関しては日本語での記述です。また、
先のメモリ配置で src
およびdst
領域は確定していますので、i
、carry
があります。
これらに相当する値として:
- 領域
src
参照用アドレス: src + i
に相当する値を0x5E番地に格納します。初期値は、src
の最下位桁を指す0x0Dです。また、1桁分の処理の開始時点では、 yレジスタにも同じ値が格納されているものとします。 - 繰り上がりの有無:
- 1桁分の処理の開始時点では下位桁での繰り上がりの有無、
処理完了時点では現行桁での繰り上がりの有無を、 0x5F番地に格納します。初期値は 0 です。
愚直な実装
まずは、
movw (%y), %a # src データの読み込み
addw $9, %y # dst の同桁位置に移動
# 9 = 16 - 7 なので、y -= 7 と等価
addw (%y), %a # src と dst の同桁を加算
jsf carry
# 現行桁同士の演算による繰り上がり無し
movw %a, (%y) # dst 位置に現時点の演算結果の保存
movw $0x0F, %y
movw (%y), %a
neqw $1, %a # 下位桁での繰り上がり判定
jsf loop_check # 下位桁からの繰り上がり無しなら
# ループ終了の判定処理へ
movw $0x0E, %y
movw (%y), %a # a に src 位置を取り出し
swap # y に src 位置を復旧
addw $9, %y # dst の同桁位置に移動
movw (%y), %a # dst 位置から演算結果の復旧
addw $1, %a # 下位桁の繰り上がり分を加算
jsf store_carry # 加算による繰り上がり有り
# 繰り上がり無し
movw %a, (%y) # dst 位置に演算結果の保存
jmp loop_check
# 現行桁同士の演算による繰り上がり有り
carry:
movw %a, (%y) # dst 位置に現時点の演算結果の保存
movw $0x0F, %y
movw (%y), %a
neqw $1, %a # 下位桁での繰り上がり判定
jsf set_carry # 下位桁からの繰り上がり無し
movw $0x0E, %y
movw (%y), %a # a に src 位置を取り出し
swap # y に src 位置を復旧
addw $9, %y # dst の同桁位置に移動
movw (%y), %a # dst 位置から演算結果の復旧
addw $1, %a # 下位桁の繰り上がり分を加算
# ここでは絶対に繰り上がりが発生しない!
# 演算結果の保存と、繰り上がり「有り」の設定
store_carry:
movw %a, (%y) # dst 位置に演算結果の保存
set_carry:
movw $1, %a
movw $0x0F, %y
movw %a, (%y) # 現行桁での繰り上がりを「有り」に設定
# ループ終了の判定処理
loop_check:
うんざりするほど長くなってしまいました。
これだけ長いプログラムになると、
単に長いだけでなく、
- 繰り上がり有無の保持領域
(メモリ上の0x5E) が、 一時的に 「現行桁」 と 「下位桁」 で共有されている - 繰り上がり判定の際にa/
yレジスタ内容が共に破壊されるため、 「演算結果の一時保存」 と 「データ位置の復旧」 が必要
という事情から、
代替レジスタの使用
先述したような長い実装になってしまうのは、
しかし、CH
、flip
命令を使用することで、
flip
命令は、
それでは、
movw (%y), %a # src データの読み込み
addw $9, %y # dst の同桁位置に移動
addw (%y), %a # src と dst の同桁を加算
jsf carry1
# 現行桁同士の演算による繰り上がり無し
flip
check_lower:
neqw $0, %b # 下位桁での繰り上がり判定
jsf lower_carry
# 「下位桁での繰り上がり無し」ないし「結果保存」
finishX:
flip
finish:
movw %a, (%y) # dst 位置に演算結果の保存
jmp loop_check # ループ終了の判定処理へ
# 下位桁での繰り上がり有り
lower_carry:
flip
addw $1, %a # 下位桁の繰り上がり分を加算
jsf carry2
# 現行桁での繰り上がり無し
jmp finish
# 下位桁での繰り上がりによる、現行桁での繰り上がり
carry2:
flip
movw $1, %z # 現行桁での繰り上がりを「有り」に設定
jmp finishX # 演算結果の保存へ
# 現行桁同士の加算による繰り上がり
carry1:
flip
movw $1, %z # 現行桁での繰り上がりを「有り」に設定
jmp check_lower # 下位桁での繰り上がり判定を実施
# ループ終了の判定処理
loop_check:
「下位桁」flip
命令で簡略化できたことから、
後は、
flip
swap # b/z の入れ替え
movw $0, %z # 現行桁での繰り上がりを「無し」に初期化
flip
計算量を無視した実装
代替レジスタを退避領域に使用することで、
しかし:
下位桁での繰り上がりの有無を保持しておく
という、
現行桁の演算で繰り上がりが発生したならば、
繰り上がりが発生しなくなるまで、 上位桁への反映を済ませてしまう
という風に発想を転換してみましょう。
movw (%y), %a # src データの読み込み
addw $9, %y # dst の同桁位置に移動
addw (%y), %a # src と dst の同桁を加算
jsf carry
# 「繰り上がり無し」ないし「結果保存」
finish:
movw %a, (%y) # 演算結果の保存
jmp loop_check # ループ終了の判定処理へ
# 繰り上がり発生
carry:
movw %a, (%y) # 演算結果の保存
addw $0x0F, %y # 15 = 16 - 1 なので、y -= 1 と等価
movw $1, %a
addw (%y), %a # 上位桁に繰り上がり分の 1 を加算
jsf carry
# 上位桁への繰り上がり無し
jmp finish
# ループ終了の判定処理
loop_check:
圧倒的にコード量を低減できました!
但し、
特別な制約条件のない通常の開発において、
あくまで、
ちなみに、
繰り上がりが続く限り、
メモリ上のデータに対する繰り上がり分の更新処理を繰り返す
という仕様が、
多倍長加算処理の実装
「1桁分の繰り上がり配慮付き加算処理」
movw $0x0D, %a # データ位置の初期値
movw $0x0E, %y
# ループ初回は終了判定を省略可能
do_add:
movw %a, (%y) # データ位置の退避
swap # y に src 位置を復旧
add_x: # 現行桁に対する加算の開始
movw (%y), %a # src データの読み込み
addw $9, %y # dst の同桁位置に移動
addw (%y), %a # src と dst の同桁を加算
jsf carry
# 「繰り上がり無し」ないし「結果保存」
finish:
movw %a, (%y) # 演算結果の保存
jmp loop_check # ループ終了の判定処理へ
# 繰り上がり発生
carry:
movw %a, (%y) # 演算結果の保存
addw $0x0F, %y # 15 = 16 - 1 なので、y -= 1 と等価
movw $1, %a
addw (%y), %a # 上位桁に繰り上がり分の 1 を加算
jsf carry
# 上位桁への繰り上がり無し
jmp finish
# ループ終了の判定処理
loop_check:
movw $0x0E, %y
movw (%y), %a # データ位置の復旧
addw $0x0F, %a # 次のデータ位置(上位桁)の算出
neqw $0x06, %a # データ位置下限との比較
jsf do_add # 処理を継続
- 終了 -
"add_
"から"loop_
" にかけての処理は、
上記のプログラムでおおよそ40ワードほどですから、