Renesas Synergy™

FAQ 1006831 : 78K0での乗除算について

四則演算、特に乗除算を行なう場合に入力データの桁数が問題になります。これは、78K0のハードウェアがどこまでサポートしているかが影響するからです。78K0での乗除算では以下のような場合が考えられます。



1.乗算の考え方(基礎)
大きいデータを小さい桁に分けて計算を行っていく(通常の筆算のやりかたと同じ)場合には位取り(桁)に注意が必要です。ここでは16進数のABCD×EFGHの計算を考えてみます。計算方法としては「MULU X命令」を使うことを考えて、CD×GH、CD×EF、AB×GH、AB×EFの2桁(8ビット)ずつ4回に分けて計算(乗算)していき、この4回の計算結果を桁に注意して加算します。
この様子を図に示します。このように、4回の計算結果をその重み(桁)を考慮して加算することで答えを得ることができます。ここでは、被乗数や乗数は4桁(16ビット)ですが、それ以上の場合にも同様に、2桁ずつの計算結果の桁を考慮して加算すれば計算することができます。


これを具体的なプログラムで考えてみます。この場合のデータ領域の例を以下に示します。(乗除算器を使用する場合を考慮して、各領域が偶数番地になるように配置します。)



;桁数定義
DNMUL           EQU     16/8    ;乗数は16ビット
DNMULC          EQU     16/8    ;被乗数は16ビット
DNRES           EQU     DNMUL + DNMULC
;データ領域の定義
RREG            DSEG    SADDRP
;下位、上位の順(リトルエンディアン)
RREG0:          DS      DNMULC  ;被乗数
RREG1:          DS      DNMUL   ;乗数
RREG2:          DS      DNRES   ;結果
CLOOP0: DS      1       ;ループカウンタ
CLOOP1: DS      1

各領域をインデックスするためのポインタの使用方法を工夫すると、効率的にアクセスすることができるようになります。ここでは、被乗数(RREG0)用と乗数(RREG1)用にポインタを使用し、結果(RREG2)はRREG1をベースにしてオフセットを付けてアクセスします。



上記の図で、DEにRREG0、HLにRREG1の各アドレスを設定し、CにはRREG2とRREG1の差を設定します。この状態で、被乗数([DE])と乗数([HL])の乗算結果の下位は[HL+C]に格納し、Cを+1して、上位を格納します。実際には単純に格納するのではなく、既に格納されているデータに加算して、桁上げを行っていきます。乗数の桁上げにはHLを+1することで対応します。次に、被乗数の桁上げはDEを+1することで対応すると同時にCも+1することで、桁上げ対応ができます。

具体的なプログラムとしては、計算結果領域をクリアしてから、被乗数の下位の桁と乗数の下位の桁の乗算、乗数を上位に移しての計算と処理していきます。

;
;  16ビット×16ビット演算
;  入力:被乗数(RREG0)、乗数(RREG1)
;  出力:計算結果(RREG2)
;
M16bitX16bit:
MOVW    HL,#RREG2-1             ; 結果領域の設定
MOV     B,#DNRES                ; 領域の大きさ
MOV     A,#0                    ; クリアの値を設定
CLRLOOP:
MOV     [HL+B],A                ; メモリのクリア
DBNZ    B,$CLRLOOP              ; 領域分を繰り返す。
;
;  領域のクリア完了
;
MOVW    DE,#RREG0               ; 被乗数のアドレス設定
MOVW    HL,#RREG1               ; 乗数のアドレス設定
MOV     CLOOP1,#DNMULC          ; 被乗数桁カウンタ
MOV     C,#DNMUL                ; 結果格納オフセット
;
;  ポインターの使い分けは以下の通り
;  被乗数にはDEレジスタ間接([DE])でアクセス
;  乗数にはHLレジスタ間接([HL])でアクセス
;  結果にはベースト・インデックス([HL+C])でアクセス
;
LOOP1:
PUSH    HL                      ; 乗数、結果のベースをセーブ
;
;  被乗数桁を固定して計算
;
MOV     CLOOP0,#DNMUL           ; 乗数桁カウンタ
LOOP2:
;
;  被乗数と乗数をセットして計算
;
PUSH    BC                      ; オフセットをセーブ
MOV     A,[DE]                  ; 被乗数の読み出し
MOV     X,A                     ; 被乗数をXにセット
MOV     A,[HL]                  ; 乗数の読み出し
MULU    X                       ; A × X  を計算
XCH     A,X                     ; Aレジスタに下位をセット
ADD     A,[HL+C]                ; 新しい結果を累積する
MOV     [HL+C],A                ; 結果を保存
;
;  計算結果の加算処理
;
LOOP3:
MOV     A,#0                    ; 上位桁用にクリア
INC     C
XCH     A,X                     ; 上位桁を取り込み
ADDC    A,[HL+C]                ; 上位桁の加算
MOV     [HL+C],A                ; 結果を保存
BC      $LOOP3                  ; 桁上げがあれば、ループ
;
;  1回分の計算完了
;
POP     BC                      ; オフセットを復帰
INCW    HL                      ; 乗数と結果の領域を桁上げ
DBNZ    CLOOP0,$LOOP2           ; 乗数を桁上げして次の桁を計算
;
;  1桁分の被乗数の計算完了
;
POP     HL                      ; 乗数、結果のベースを復帰
INCW    DE                      ; 被乗数の桁上げ
INC     C                       ; 結果保存用オフセットを桁上げ
DBNZ    CLOOP1,$LOOP1           ; 被乗数の次の桁の計算に
;
;  必要な桁の計算完了
;

同じ処理を乗除算器で処理してみます。こちらは1回の処理で完了します。

M16bitX16bit2:
MOVW    AX,RREG0                ; 被乗数の取り込み
MOVW    MDA0L,AX                ; 被乗数のセット
MOVW    AX,RREG1                ; 乗数の取り込み
MOVW    MDB0,AX                 ; 乗数のセット
MOV     DMUC0,#10000001B        ; 乗算を開始
BT      DMUE,$$                 ; 演算完了待ち
MOVW    AX,MDA0L                ; 結果の下位を読み出し
MOVW    RREG2,AX                ; 結果の格納
MOVW    AX,MDA0H                ; 上位の読み出し
MOVW    RRE2+2,AX               ; 結果の格納

2.桁数が大きくなった場合の応用
桁数が多くなった場合の乗算は、基本的に上で説明してきた内容を繰り返すだけとなります。特に、「MULU X」を用いたプログラムは定数「DNMULC」と「DNMUL」を変更するだけで乗算の桁数を変更できます。

乗除算器を用いた場合には、「MULU X」を用いたと同じように、乗算の結果を累積していくことで対応できます。ただし、結果が4バイトになるので結果の累積部分をループするには注意が必要です。(ループを回すにはAXレジスタだけでなく、例えばDEレジスタを使って「00」→「D」→「E」→「A」→「X」とシフトしていけば処理可能です。ただし、4バイト分は必ず処理する必要があります。)また、作業用として、RREG2の後ろに余分に2バイトの領域を確保します。その他には、定数「DNMULC」と「DNMUL」を「32/8」に変更して32ビット×32ビットの計算とします。

RREGW:          DS      2               ; 作業用

このプログラム例は以下のようになります。

;************************************************
;  32ビット×32ビット演算部本体
;  入力:被乗数(RREG0)、乗数(RREG1)
;  出力:計算結果(RREG2)
;************************************************

M32bitX32bitB:
MOVW    HL,#RREG2-1             ; 結果領域の設定
MOV     B,#DNRES                ; 領域の大きさ
MOV     A,#0                    ; クリアの値を設定
CLRLOOP:
MOV     [HL+B],A                ; メモリのクリア
DBNZ    B,$CLRLOOP              ; 領域分を繰り返す。
;
;  領域のクリア完了
;
MOVW    DE,#RREG0               ; 被乗数のアドレス設定
MOVW    HL,#RREG1               ; 乗数のアドレス設定
MOV     CLOOP1,#DNMULC/2        ; 被乗数桁カウンタ
MOV     C,#DNMUL                ; 結果格納オフセット
MOV     B,#4-1                  ; 乗除算器結果の桁数-1を設定
;
;  ポインターの使い分けは以下の通り
;  被乗数にはDEレジスタ間接([DE])でアクセス
;  乗数にはHLレジスタ間接([HL])でアクセス
;  結果にはベースト・インデックス([HL+C])でアクセス
;
LOOP1:
PUSH    HL                      ; 乗数、結果のベースをセーブ
;
;  被乗数桁上げ
;
MOV     CLOOP0,#DNMUL/2         ; 乗数桁カウンタ
;
;  被乗数のセット
;
MOV     A,[DE]                  ; 被乗数の下位8bit読み出し
MOV     X,A                     ; 被乗数をXにセット
INCW    DE
MOV     A,[DE]                  ; 被乗数の上位8bit読み出し
MOVW    MDB0,AX                 ; 被常数を乗除算器にセット
LOOP2:
PUSH    BC                      ; オフセットをセーブ
;  乗数のセット
MOV     A,[HL]                  ; 乗数の下位8bit読み出し
MOV     X,A                     ; 下位をXレジスタへ
MOV     A,[HL+1]                ; 乗数の上位8bit読み出し
MOVW    MDA0L,AX                ; 乗数のセット
;  計算処理開始
MOV     DMUC0,#10000001B        ; 乗算開始
BT      DMUE,$$                 ; 演算完了待ち
;  計算処理完了
MOVW    AX,MDA0H                ; 結果の上位16ビット読み出し
MOVW    RREGW,AX                ; 結果を作業領域に保存
MOVW    AX,MDA0L                ; 結果の下位16ビット読み出し
;
;  今回の結果を計算結果領域に加算
;
XCH     A,X                     ; Aレジスタに下位をセット
ADD     A,[HL+C]                ; 新しい結果を累積する
MOV     [HL+C],A                ; 結果を保存
;
;  計算結果の加算処理
;
LOOP3:
MOV     A,#0                    ; 上位桁用にクリア
XCH     A,RREGW+1               ; 最上位桁にシフト
XCH     A,RREGW                 ; 次の桁にシフト
XCH     A,X                     ; 次の桁をAレジスタに
INC     C
ADDC    A,[HL+C]                ; 上位桁の加算
MOV     [HL+C],A                ; 結果を保存
DBNZ    B,$LOOP3                ; 4バイト分は繰り返す
INC     B ; 次回以降必ず下に抜けるためのダミー
BC      $LOOP3                  ; 桁上げがあれば、ループ
;
;  1回分の計算完了
;
POP     BC                      ; オフセットを復帰
INCW    HL                      ; 乗数と結果の領域を桁上げ
INCW    HL                      ; 16ビット分桁上げ
DBNZ    CLOOP0,$LOOP2           ; 乗数を桁上げして次の桁を計算
;
;  1桁分の被乗数の計算完了
;
POP     HL                      ; 乗数、結果のベースを復帰
INCW    DE                      ; 被乗数の桁上げ
INC     C                       ; 結果保存用オフセットを桁上げ
INC     C                       ; 16ビット分桁上げ
DBNZ    CLOOP1,$LOOP1           ; 被乗数の次の桁の計算に
;
;  必要な桁の計算完了
;

このプログラムでは、できるだけサイズを小さくするために乗除算器の演算結果を加算する部分をループで処理しています。そのために、4バイト分までの加算は「DBNZ B」命令でループしますが、その後は「BC」命令でループします。そこで、一度「DBNZ B」命令を下に抜けると以降は必ず下に抜けるように「INC B」命令を実行して、Bレジスタを1にしています。ちなみに、この部分を分かり易く記述するには「INC B」命令の代わりに以下の朱書きの5命令が必要になります。

DBNZ    B,$LOOP3                ; 4バイト分は繰り返す
LOOP4:
BNC     $NEXT
MOV     A,#0
INC     C                       ; 次の桁へ
ADDC    A,[HL+C]                ; 桁上げ部分の計算
MOV     [HL+C],A                ; 結果の保存

BR      $LOOP4                  ; ループ
NEXT:

3.除算の考え方(基礎)
除算の除数は分割できないので、除数に応じた計算方法が必要になります。基本的に除数が使用予定のハードウェア(除算命令を含む)で対応しているビット長を超えるとそのハードウェアは使用できません。被除数については、桁数が大きい場合でもハードウェアで処理できるビット長以下に分割すれば処理可能です。ハードウェアで処理できるビット長以下であればよく、処理できるビット長に無理して合わせる必要はありません。これは、実際の筆算の処理と同じように、除数の桁に合わせて処理をするのが簡単です。
桁の管理については、計算結果の商は最大で被除数と同じ桁になるので、被除数の大きい方の桁から埋めていけば良いことになります(結果そのものは値が0になっていることがあります)。8桁(32ビット)を2桁(8ビット)で割る場合の考え方の例を示します。



これを実際のプログラムにすると以下のようになります。
まず、定数の定義は以下のように32ビット割る8ビットに対応した値となります。定数「DNdivD」の値を変化させることで、被除数の桁数を変更できます。「32/8」の部分を「40/8」にすると40ビット割る8ビット、「64/8」にすると64ビット割る8ビットなどとなります。

;************************************************
;       定数や変数の定義
;************************************************
;
;  計算回数の定義(データのバイト長で定義)
DNdivS          EQU     8/8             ; 除数は8ビット
DNdivD          EQU     32/8            ; 被除数は32ビット

これを用いて、作業用のデータ領域は以下のように定義します。被除数と商は同じ領域に配置します。

;
;データ領域の定義
;
RREG            DSEG    SADDRP
;
;  データは下位、上位の順(リトルエンディアン)とする
;
;  データ領域
RREG0:          DS      DNdivD          ; 被除数/商
RREG1:          DS      DNdivS          ; 剰余
RREG2:          DS      DNdivS          ; 除数

プログラム本体は以下のように簡単な処理になります。このプログラムで、LOOP1からの部分が実際の計算を行う部分です。その直前でCレジスタを0にすることで、1回目の計算での被除数の上位を0にしています。この処理を行うことで、一つの繰り返しループだけで処理できるようにしています(この処理方法は、割り算の回数が4回必要なので、その分は処理時間が長くなりますが、毎回の除算で得られた商は必ず下位8ビットに収まるので、処理が簡単になります)。

CSEG
;************************************************
;  32ビット÷8ビット演算部本体
;  入力:被除数(RREG0)、除数(RREG2)
;  出力:商(RREG0)、剰余(RREG1)
;************************************************

D32bit_8bit:
MOVW    HL,#RREG0+DNdivD-1      ; 被除数のアドレス設定
MOV     B,#DNdivD               ; 被除数数桁カウンタ
;
;  ポインタの使い分けは以下の通り
;  被乗数/商にはHLレジスタ間接([HL])でアクセス
;
MOV     C,#0                    ; 剰余(被除数の上位)をクリア
LOOP1:
;
;  被除数、除数のセット
;
MOV     A,RREG2                 ; 除数の読み出し
XCH     A,C                     ; 除数をCレジスタにセット
MOV     X,A                     ; 前回の剰余をXに退避
MOV     A,[HL]                  ; 被除数の読み出し
XCH     A,X                     ; 被除数をAXにセット
;
DIVUW   C                       ; 除算の実行

MOV     A,X                     ; 商の読み出し
MOV     [HL],A                  ; 商をセット
DECW    HL                      ; ポインタを下の桁へ
DBNZ    B,$LOOP1                ; 必要回数ループ
;
MOV     A,C                     ; 剰余の読み出し
MOV     RREG1,A                 ; 剰余のセット

;
;  必要な桁の計算完了
;

今度は、同様に乗除算器を用いた例を考えます。32ビット割る16ビットは1回の処理で完了してしまうので、上記の除算命令使用時との比較のために参考で示します。

CSEG
;************************************************
;  32ビット÷8ビット演算部本体
;  入力:被除数(RREG0)、除数(RREG2)
;  出力:商(RREG0)、剰余(RREG1)
;************************************************

D32bit_8bitB:
;
;  被除数、除数のセット
;
MOVW    AX,RREG0                ; 被除数の下位データ読み出し
MOVW    MDA0L,AX                ; 乗除算器へのセット
MOVW    AX,RREG0+2              ; 被除数の上位データ読み出し
MOVW    MDA0H,AX                ; 乗除算器へのセット
MOV     A,RREG2                 ; 除数の読み出し
MOV     X,A                     ; 下位バイトにセット
MOV     A,#0                    ; AXレジスタに除数をセット
MOVW    MDB0,AX                 ; 除数を乗除算器にセット
;
;  計算処理開始
MOV     DMUC0,#10000000B        ; 除算開始
BT      DMUE,$$                 ; 演算完了待ち
;  計算処理完了

MOVW    AX,MDA0L                ; 商の下位読み出し
MOVW    RREG0,AX                ; 商領域に設定
MOVW    AX,MDA0H                ; 商の上位読み出し
MOVW    RREG0+2,AX              ; 商領域に設定

MOV     A,SDR0L                 ; 剰余の下位を読み出し
MOV     RREG1,A                 ; 剰余領域に設定
;
;  必要な桁の計算完了
;

4.除数が16ビットのときの処理
この場合には乗除算器を用いて計算することになります。乗除算器を内蔵していないデバイスの場合には、「5.除数が16ビットを超える場合の処理」と同じ処理となります。
このプログラムの基本的な考え方は「3.除算の考え方(基礎)」で説明した除算命令を用いた除算と同じです。違いは1回に処理するのが、16ビット単位になることと除算命令ではなく乗除算回路を使用することです。
具体的なプログラム例を以下に示します。このプログラムでは、以下に示すように定数「DNdivS」が変更され、除数や剰余の桁数が16ビットになった以外は変数領域で違いはありません。

DNdivS          EQU     16/8            ; 除数は16ビット
DNdivD          EQU     32/8            ; 被除数は32ビット


CSEG
;************************************************
;  32ビット÷16ビット演算部本体
;  入力:被除数(RREG0)、除数(RREG2)
;  出力:商(RREG0)、剰余(RREG1)
;************************************************

D32bit_16bit:

MOVW    HL,#RREG0+DNdivD-2      ; 商のアドレス設定
MOV     B,#DNdivD/2             ; 被除数桁カウンタ
;
;  ポインターの使い分けは以下の通り
;  被乗数/商にはHLレジスタ間接([HL])でアクセス
;
MOVW    AX,RREG2                ; 除数の読み出し
MOVW    MDB0,AX                 ; 除数を乗除算器にセット
MOVW    DE,#0                   ; 前回の剰余を0にしておく
LOOP1:
;
;  被除数のセット
;
MOV     A,[HL]                  ; 被除数の下位バイト読み出し
MOV     X,A
MOV     A,[HL+1]                ; 上位バイト読み出し
MOVW    MDA0L,AX                ; 乗除算器にセット
MOVW    AX,DE                   ; 前回剰余を読み出し
MOVW    MDA0H,AX                ; 上位桁には前回剰余をセット
;
;  計算処理開始
MOV     DMUC0,#10000000B        ; 除算開始
BT      DMUE,$$                 ; 演算完了待ち
;  計算処理完了


MOVW    AX,MDA0L                ; 商の読み出し
MOV     [HL+1],A                ; 商の上位バイトを格納
MOV     A,X                     ; 商の下位バイトをセット
MOV     [HL],A                  ; 商の下位バイト格納
MOVW    AX,SDR0                 ; 剰余の読み出し
MOVW    DE,AX                   ; 剰余をセーブ
DECW    HL                      ; ポインタを下の桁へ
DECW    HL
DBNZ    B,$LOOP1                ; 必要回数ループ
;
MOVW    RREG1,AX                ; 剰余を格納

;
;  必要な桁の計算完了
;

5.除数が16ビットを超える場合の処理
この場合には、基本的に1ビットずつずらしながら被除数から除数を引いていき、引ければ商のそのビットが1になります。引けなければ0になります。このような計算を行う場合に注意する必要があるのが、商の桁位置(ビット位置)です。引き算そのものがビットをずらしながら引いていくので、商1回の引き算ごとに1ビットずらしながら毎回の結果を付け加えていくことになります。例えば、32ビットの被除数を16ビットで割る場合を考えます。引き算は上位ビットから計算していきますが、除数が最低で1の場合もあるので、被除数と除数の関係は以下に示すような状態から計算を開始する必要があります。この状態で引けた場合には、その結果(の1ビット)には2の31乗の重み(桁)があります。



次の桁を計算するには、被乗数を左にシフトして、同様に引き算を行えば良いことになります。また、同時に前回の商も左シフトすることで、今回の結果との桁の関係が正しくなります。



この処理を被除数の桁数分だけ繰り返すと最後のビットの桁の計算時には以下のようになります。これをまとめると、被除数の上位に除数-1個の0の桁を付加し、被除数を左シフトしながら引き算を行い、商のビットも左シフトし、それを被除数の桁数分だけ繰り返すことで除算を行うことができます。



実際のプログラムでは、使用する変数の使い方と変数の並びに注意が必要です。被除数、商及び引き算用の作業領域は1ビットの計算毎に左シフトする必要があります。これらのシフトを連続して処理すればプログラムが効率的になります。また、商の桁数は最大で被除数と同じ桁数になるので、同じ領域を兼用できれば必要なメモリ(RAM)領域が少なくできますし、同時に左シフトを行うことができます。除算を行う前には商は当然何もなく、被除数が31ビットあるだけです。下図に示すように、計算を進めるに従って、被除数の下位から未使用のビットが追加されていきます。ビットごとの計算完了後にそのビットの商を未使用のビットに格納すれば、商のための領域を独立して準備する必要はなく、被除数の左シフトで商も同時に左シフトできます。このように被除数領域と商領域を共用することで、メモリ領域も演算処理も効率的なプログラムが可能になります。



次に計算領域について考えてみます。計算のための領域として除数と同じビット長の領域を確保する必要があり、被除数はMSBから順に計算領域の右からシフトインしてきます。ここで、単純に計算領域から除数を引いていくだけでは問題があります。具体的な例で示すと、除数の上位2ビットが11の場合などに注意が必要です。計算領域の上位2ビットが10の場合には除数を引くことはできないので、そのビットの商は0になります。次の計算のために計算領域を左シフトすると、計算領域のMSBの1はなくなってしまいます(実際にはキャリー・フラグに入ります)。ところが、実際の値は16ビットではなく、17ビットです。除数は16ビットなので、この場合にはそのビットの商は必ず1になります。



つまり、計算領域からの桁上がりを商の領域(被除数に使用していた領域)のLSBに反映させれば、対応できます。当然、この桁上がりがないときには計算領域からの除数の減算結果で商は決まります。このような処理を行うことで、正しい計算を行うことができます。

実際のプログラムにすると以下のようになります。桁数としては以下の例では32ビットの被除数を16ビットの除数で割る場合の例です。

;************************************************
;       定数や変数の定義
;************************************************
;
;  計算回数の定義(データのバイト長で定義)
DNdivS  EQU     16/8            ; 除数は16ビット
DNdivD  EQU     32/8            ; 被除数は32ビット
DNQUO   EQU     DNdivD*8        ; 計算回数

これを用いて、作業用のデータ領域は以下のように定義します。被除数と剰余は同じ領域に配置します。

;
;データ領域の定義
;
RREG    DSEG    SADDR
;
;  データは下位、上位の順(リトルエンディアン)とする
;
;  データ領域
RREG0:  DS      DNdivS          ; 計算領域/剰余
RREG1:  DS      DNdivD          ; 被除数/商
RREG2:  DS      DNdivS          ; 除数

これらの定数や変数を用いた実際の計算処理部分のプログラムは以下のようになります。

CSEG
;*******************************************************
;       32bit÷16bit演算本体部
;       入力:被除数(RREG1)、除数(RREG2)
;       出力:商(RREG1) 、剰余(RREG0)
;*******************************************************
D32bit_16bit:
;
;  作業領域のクリア
;
MOV     A,#0
MOV     C,#DNdivS               ; 作業領域の大きさ設定
MOVW    HL,#RREG0-1
;
;  ベースをRREG0-1にすることで、Cの値(DNdivS~1)で[HL+C]が
;RREG0の最後(RREG0+DNdivS-1)からRREG0の先頭までを指せる。
;
CLOOP0:
MOV     [HL+C],A                ; メモリのクリア
DBNZ    C,$CLOOP0               ;

MOV     B,#DNQUO                ; bit演算回数設定
JDIV120:
;
;  1桁(bit)の計算のための準備
;
;****************************************
;作業領域に被除数のMSBを1bitシフト移動
;****************************************
CLR1    CY                      ; キャリー情報のクリア

MOV     C,#DNdivS+DNdivD        ; 演算回数(作業領域+被除数分)
MOVW    HL,#RREG0               ;
SLLOOP:
MOV     A,[HL]                  ; RREG1からの桁上げ
ROLC    A,1                     ; 用にLSBは0にして
MOV     [HL],A                  ; 次の桁演算準備で
INCW    HL                      ; 左シフトしておく。
DBNZ    C,$SLLOOP               ;

MOV1    RREG0.0,CY              ; 被除数-->RREG0のLSB
;
;  RREG0からRREG1を引くことで1桁分の演算を行なう
;なお、ここまででRREG1のLSBが1なら、被除数は17ビットとなり、
;除数より大きいので引き算の結果のボローを確認しなくてよい
;
;****************************************
;被除数から除数を引く
;****************************************
CLR1    CY                      ; 初期値としてCYクリア
MOVW    HL,#RREG2               ; 除数先頭アドレス
MOVW    DE,#RREG0               ; 被除数先頭アドレス
MOV     C,#DNdivS               ; 演算回数(除数バイト分)
SUBLOOP:
MOV     A,[DE]                  ;
SUBC    A,[HL]                  ; 被除数から除数を減算
MOV     [DE],A                  ; 演算結果を戻す
INCW    DE                      ; 被除数次アドレス
INCW    HL                      ; 除数次アドレス
DBNZ    C,$SUBLOOP              ; 演算回数分繰り返す
BT      RREG1.0,$JDIV220        ; 被除数が17bitなら次へ

BNC     $JDIV210                ; 引ければ分岐

;****************************************
;被除数を元に戻す
;****************************************
JDIV180:
CLR1    CY                      ; キャリー情報のクリア
MOVW    HL,#RREG2               ; 除数先頭アドレス
MOVW    DE,#RREG0               ; 被除数先頭アドレス
MOV     C,#DNdivS               ; 演算回数(除数バイト分)
ADJLOOP:
MOV     A,[DE]                  ;
ADDC    A,[HL]                  ; 被除数に除数を加算
MOV     [DE],A                  ; 演算を元に戻す
INCW    DE                      ; 除数次アドレス
INCW    HL                      ; 被除数次アドレス
DBNZ    C,$ADJLOOP              ; 演算回数分繰り返す
JDIV210:
NOT1    CY                      ; キャリーを反転
MOV1    RREG1.0,CY              ; 商のビットに反映
JDIV220:
DBNZ    B,$JDIV120              ; 演算をDNCNT桁分完了する
; まで繰り返す

このプログラムは以下の定数の定義部分で赤い背景の値を変更することで除数のビット長を8ビット単位で変更できます。同様に青い背景の値を変更することで被除数のビット長も8ビット単位で変更できます。

DNdivS  EQU     16/8            ; 除数は16ビット
DNdivD  EQU     32/8            ; 被除数は32ビット

6.除数が16ビットを超える場合の処理プログラムについての考察
上で説明したプログラムはかなりサイズを意識したプログラム(約70バイト)になっています。このような計算プログラムではプログラムのサイズ以外にも処理時間も問題になってきます。このプログラムでは、除数が1で被除数が全ビット1のときに最も処理時間が短くなります(64bit÷32bitで考えると26,318クロック)。除数>被除数の場合には毎回引きすぎた分を元に戻す処理が発生することで、処理時間が一番長く(64bit÷32bitで考えると34,126クロック)なり、約7,800の差があります。通常のリアルタイム性が要求される組込み制御では、この最悪の時間を処理時間として、考慮する必要があります。
これに対して、下記に示すプログラムのように、引き過ぎ分を元に戻す必要がないように処理を変更すると、同じ条件の計算で28,372~28,474クロックと最短時間は約2,000クロック長くなり、プログラムも16バイト長くなります。しかし、最悪の計算時間は約5,600クロックも短くなります。
(平均計算時間:除数桁数を16ビット~32ビットと考え、0/1の確率が50%とすると、64回の計算中最初の8回は結果が0で残り56回のうちの50%で結果が0になるとすると、36回は結果が0となり、前述のプログラムの処理予測値としては約31,000クロックに対して下記のプログラムは28,400クロックと1割以上高速に処理できることになります。つまり、リアルタイム性がそれほど必要ない場合にも下記のプログラムの方が高速に処理できることになります。)どちらが望ましいかはプログラムに対する要求項目の優先度に依存します。

;****************************************************************
;*                                                              *
;*      64bit÷32bitサブルーチン                                *
;*       INPUT  :RREG1に被除数, RREG0に除数                     *
;*       OUTPUT :RREG1に商, RREG2に剰余                         *
;*                                                              *
;*  RREG1の上位にRREG2を加えて96bitにしてから除数を             *
;*引いていく。実際の引き算はRREG2からRREG0を引いていく。        *
;*引ければ、その回(bit)の答えは1となり、RREG1のLSBに            *
;*結果がMSBから順に得られていく。                               *
;*(更にRREG1とRREG2を合わせて左にシフトすることで被除数         *
;*と答えのbitを2倍していき、次の桁(bit)の計算に移る。           *
;*      +←(RREG2/RREG3)←(RREG1)←+  ・・・左シフト            *
;*      |                          |                            *
;*      +------(結果のビット)----→+                            *
;*なお、引き算の結果のRREG2のMSBが1の場合には次の桁の           *
;*計算結果は無条件に1となるので、シフトした結果のRREG1の        *
;*LSBの値1がそのままその桁の結果となる。)                       *
;*                                                              *
;****************************************************************

;********************************************************
;       定数や変数の定義
;********************************************************
;
;計算回数の定義(データのバイト長で定義)
;
DNdivS  EQU     (32/8)                  ; 除数(32bit)
DNdivD  EQU     (64/8)                  ; 被除数(64bit)
DNQUO   EQU     DNdivD*8                ; 演算回数
;
;データ領域の定義
;
DREG    DSEG    SADDRP
RREG0:  DS      DNdivS                  ; 除数領域
RREG1:  DS      DNdivD                  ; 被除数及び商領域
RREG2:  DS      DNdivS                  ; 作業及び剰余領域
RREG3:  DS      DNdivS                  ; 減算領域
RCOUNT: DS      1                       ; 演算回数カウンタ
Offset0 EQU     RREG3-RREG0             ; 減算結果用オフセット

PUBLIC  D64bit_32bit
PUBLIC  RREG0
PUBLIC  RREG1
PUBLIC  RREG2

CSEG
;*******************************************************
;       64bit÷32bit演算本体部
;       入力:被除数(RREG1)、除数(RREG0)
;       出力:商(RREG1)、剰余(RREG2)
;*******************************************************
D64bit_32bit:
;
;  作業領域のクリア
;                                       ↓:クロック数
MOV     A,#0                    ;4:
MOV     B,#DNdivS               ;4:作業領域の大きさ設定
MOVW    HL,#RREG2-1             ;6:
;                                       (20)
;
;  ベースをRREG2-1にすることで、Cの値(DNdivS~1)で[HL+B]が
;RREG2の最後(RREG2+DNdivS-1)からRREG2の先頭までを指せる。
;
CLOOP0:
MOV     [HL+B],A                ;6:メモリのクリア
DBNZ    B,$CLOOP0               ;6:
;(12*4=48)

MOV     RCOUNT,#DNQUO           ;6:bit演算回数設定
MOV     B,#DNdivS               ;4:
;                                       (10)

JDIV120:
;
;  1桁(bit)の計算のための準備
;
;****************************************
;作業領域に被除数のMSBを1bitシフト移動(270)
;****************************************
CLR1    CY                      ;2:キャリー情報のクリア

MOV     C,#DNdivD               ;4:被除数桁分カウンタ設定
MOVW    HL,#RREG1               ;6:被除数領域ポインタ設定
SLLOOP:
MOV     A,[HL]                  ;4:作業領域からの桁上げ用に
ROLC    A,1                     ;2:回転してLSBは0に
MOV     [HL],A                  ;4:次の桁演算準備で被除数
INCW    HL                      ;4:領域を左シフトしておく。
DBNZ    C,$SLLOOP               ;6:被除数のバイト数分ループ
;(12)+(20*8=160)

MOV     C,#DNdivS               ;4:作業領域分カウンタ設定
SLLOOP2:
MOV     A,[HL+B]                ;6:減算/作業領域のデータを
ROLC    A,1                     ;2:次の桁演算準備で左シフト
MOV     [HL],A                  ;4:して、作業領域にセットする。
INCW    HL                      ;4:
DBNZ    C,$SLLOOP2              ;6:
;                                       (4)+(22*4=88)

MOV1    RREG1.0,CY              ;6:被除数-->RREG1のLSB
;
;  RREG2からRREG0を引くことで1桁分の演算を行なう
;なお、ここまででRREG1のLSBが1なら、被除数は17ビットとなり、
;除数より大きいので引き算の結果のボローを確認しなくてよい
;
;****************************************
;被除数から除数を引く
;****************************************
CLR1    CY                      ;2:初期値としてCYクリア
MOVW    HL,#RREG0               ;6:除数先頭アドレス
MOVW    DE,#RREG2               ;6:作業領域先頭アドレス
MOVW    BC,#DNdivS*100H+Offset0 ;6:B:演算回数(除数バイト分)
; C:減算結果用オフセット
;                                       (20)
SUBLOOP:
MOV     A,[DE]                  ;4:
SUBC    A,[HL]                  ;4:被除数から除数を減算
MOV     [HL+C],A                ;6:演算結果を減算領域に
INCW    DE                      ;4:被除数次アドレス
INCW    HL                      ;4:除数次アドレス
DBNZ    B,$SUBLOOP              ;6:演算回数分繰り返す
;                                       (28*4=112)

MOV     B,#DNdivS               ;4:次回オフセット設定
BT      RREG1.0,$JDIV220        ;10:被除数が17bitなら次へ
NOT1    CY                      ;2:キャリーを反転
MOV1    RREG1.0,CY              ;6:商のビットに反映
BC      $JDIV220                ;6:引けたら分岐する
MOV     B,#0                    ;4:前回結果は使わない
;                                       (32)

JDIV220:
DBNZ    RCOUNT,$JDIV120         ;8:演算をDNQUO桁分完了する
; まで繰り返す
;****************************************
;  最後の減算結果を元に戻す(引けた場合)
;****************************************
BF      RREG1.0,$JDIV310        ;10:引けなかった場合終了
MOVW    HL,#RREG2               ;6:剰余先頭アドレス
MOVW    BC,#DNdivS*101H         ;6:演算回数(除数バイト分)
ADJLOOP:
MOV     A,[HL+B]                ;6:減算結果読み出し
MOV     [HL],A                  ;4:剰余領域にコピー
INCW    HL                      ;4:結果を次アドレスに
DBNZ    C,$ADJLOOP              ;6:剰余桁回数分繰り返す
;                                       (22)+(20*4=80)
JDIV310:
RET                             ;6:

 

適用製品

78K ファミリ
他にご質問がございましたら、リクエストを送信してください