10進表示


このプログラムで、Z80 vs 6502 の比較は一旦終了。

割り算ルーチンがあるものとして、10進表示ルーチンを作ります。


目次

Z80 vs 6502 トップページ(別ページ)

概要

Z80基本

BCD命令ひたすら引き算

6502基本

ひたすら引き算

速度判定


概要

16bit の数値を 10進数で表示するための、文字列を作ります。


文字列は ASCII で、末尾に 0(ヌル文字) が入るとします。つまり、普通の C 言語文字列として扱える、上位 4bit が 3 であるゾーンBCD形式を作ります。


文字列は DECIMAL: とラベルのつけられたアドレスから格納します。

16bit なので最大5桁ですが、末尾の 0 も含めた 6byte 分のメモリが確保されています。

(高速化のため必要であれば、もっと多くのメモリがあると想定しても構いません)


文字列の頭は 0 や空白を付けず、10進の最上位桁から始まるようにしてください。

(文字列は常に5桁ではなく、1~5桁になります)


DIV というラベルで、割り算ルーチンを呼び出せるものとします。


Z80 では DE に被除数 A に除数を入れて呼び出すと、DE に商、A に剰余が帰ります。

6502 では、>0 >1 に被除数 A に除数を入れて呼び出すと、>0 >1 に商、Aに剰余が帰ります。


仮に、6502 では 400クロック、Z80 では 800 クロックが除算にかかるものとします。


MUL というラベルで、16bit × 8bit の掛け算ルーチンを呼び出せるものとします。

(ただし、結果は 16bit で返ります。被乗数はあまり大きく設定できません)


値の受け渡し法は DIV に準じます。

16bit の被乗数が破壊されて、結果が帰ります。

6502 で 150クロック、Z80 で 300 クロックとします。


乗除算ルーチンは共に、他のレジスタやメモリは破壊されないものとします。


8086 という数値を10進で文字列化してください。


除算はただでさえ重い処理ですが、ここで桁数だけ繰り返し呼び出されます。


表示用の10進文字列を作る処理は、非常に重たいことになります。

(実際には、任意の数字での割り算ルーチンではなく、10で割った商と剰余を求める専用ルーチンを用意したりするのですが)


Z80基本



	LD HL,DECIMAL+5 ; 10 (11)
	LD (HL),0 ; 10 (11)
	LD DE,8086 ; 10 (11)
	LD BC,1 ; 10 (11)
LOOP:
	LD A,10 ; 7 (8)
	CALL DIV ; 17 (18) + 800
	OR 30H ; 7 (8)
	DEC HL ; 6 (7)
	LD (HL),A ; 7 (8)
	INC C ; 4 (5)
	LD A,D ; 4 (5)
	OR E ; 4 (5)
	JR NZ,LOOP ; 12 / 7 (13 / 8)

AFTER:
	LD A,C ; 4 (5)
	CP 6 ; 7 (8)
	JR Z,END ; 12 / 7 (13 / 8)
	LD DE,DECIMAL ; 10 (11)
	LDIR ; 21 / 16 (23 / 18)
END:

これがやりたくて前回除算を作ったのだけど、除算の存在を前提にすると簡単すぎるな (^^;


数値の10進化、というと、10で割った余りをスタックに一旦貯めて、後で取り出しながら並べる、と言うのが基本です。

しかし、Z80 のスタックは 16bit 単位のアクセスしかできません。

ここでは、仮に右詰で並べてから、ブロック転送命令で左詰めしています。


ブロック転送は過去のプログラムで高速化の手法がありましたので、もっと高速化できそうですが「基本」なので簡単に…


前処理が 44 クロック

ループ内が、1桁の処理で 77+800 クロック。800クロックは除算ルーチンです。

ただし、最後の桁は5クロック速くなります。


4桁あるので、877*4 -5 = 3503 クロックになります。


後処理も桁数で異なりますが、4 桁の時が最も遅く、119 クロック。


44+3503+119 = 3666


総計は、3666 クロックになります。


BCD命令

2014.9.1 追記

わー、大作だぁ…。

BCD 命令を使った、と投稿されてきたプログラムです。



	LD BC,8086 ; 10 (11)
	LD DE,NTABLE ; 10 (11)
	LD HL,DECIWK ; 10 (11)

DIV2LOOP:
	SRL B ; 8 (10)
	RR C ; 8 (10)
	JR NC,SKIP ; 12 / 7 (13 / 8)

	LD A,(DE) ; 7 (8)
	ADD A,(HL) ; 7 (8)
	DAA ; 4 (5)
	LD (HL),A ; 7 (8)
	INC E ; 4 (5)
	INC L ; 4 (5)

	LD A,(DE) ; 7 (8)
	ADC A,(HL) ; 7 (8)
	DAA ; 4 (5)
	LD (HL),A ; 7 (8)
	INC E ; 4 (5)
	INC L ; 4 (5)

	LD A,(DE) ; 7 (8)
	ADC A,(HL) ; 7 (8)
	LD (HL),A ; 7 (8)
	INC E ; 4 (5)
	DEC L ; 4 (5)
	DEC L ; 4 (5)
	JP DIV2LOOP ; 10 (11)

SKIP:
	INC E ; 4 (5)
	INC E ; 4 (5)
	INC E ; 4 (5)
	LD A,B ; 4 (5)
	OR C ; 4 (5)
	JP NZ,DIV2LOOP ; 12 / 7 (13 / 8)

PACK2ZONE:
	LD DE,DECIMAL ; 10 (11)
	INC L ; 4 (5)
	INC L ; 4 (5)
	LD B,30H ; 7 (8)
	LD A,(HL) ; 7 (8)
	OR A ; 4 (5)
	JR NZ,ST5 ; 12 / 7 (13 / 8)
	DEC L ; 4 (5)
	RLD ; 18 (20)
	JR NZ,ST4 ; 12 / 7 (13 / 8)
	RLD ; 18 (20)
	JR NZ,ST3 ; 12 / 7 (13 / 8)
	DEC L ; 4 (5)
	RLD ; 18 (20)
	JR NZ,ST2 ; 12 / 7 (13 / 8)
	RLD ; 18 (20)
	OR B ; 4 (5)
	JP ST1 ; 10 (11)
ST5:
	OR B ; 4 (5)
	LD (DE),A ; 7 (8)
	INC E ; 4 (5)
	DEC L ; 4 (5)
	RLD ; 18 (20)
ST4:
	OR B ; 4 (5)
	LD (DE),A ; 7 (8)
	INC E ; 4 (5)
	RLD ; 18 (20)
ST3:
	OR B ; 4 (5)
	LD (DE),A ; 7 (8)
	INC E ; 4 (5)
	DEC L ; 4 (5)
	RLD ; 18 (20)
ST2:
	OR B ; 4 (5)
	LD (DE),A ; 7 (8)
	INC E ; 4 (5)
	RLD ; 18 (20)
ST1:
	LD (DE),A ; 7 (8)
	INC E ; 4 (5)
	XOR A ; 4 (5)
	LD (DE),A ; 7 (8)
END:


NTABLE:
	DB 01H,00H,00H
	DB 02H,00H,00H
	DB 04H,00H,00H
	DB 08H,00H,00H
	DB 16H,00H,00H
	DB 32H,00H,00H
	DB 64H,00H,00H
	DB 28H,01H,00H
	DB 56H,02H,00H
	DB 12H,05H,00H
	DB 24H,10H,00H
	DB 48H,20H,00H
	DB 96H,40H,00H
	DB 92H,81H,00H
	DB 84H,63H,01H
	DB 68H,27H,03H

DECIWK:
	DB 00H,00H,00H;

DECIMAL:
	DB 00H,00H,00H,00H,00H,00H;

BCD 命令と言うのは、2進化10進、と呼ばれる方法で足し算・引き算を行う際に補助的に使用する命令です。

2進化10進については、詳しくは過去に書いた日記を見てね。


まぁ、16進数なのに、A~F を使わないで 10進数を表記したものだと思ってください。


で、BCD を使うことで 10進変換なんて出来たの!?

と思い詳細に読んでみると…あーなるほど。驚きの超絶技巧、と言うわけではないのですが、巧妙な高速化が行われています。


プログラムは大きく前半と後半に別れます。

まず、前半から解説。


NTABLE の中身は、16bit の「重み」を、あらかじめ BCD で表記したものです。

1 ~ 32768 までで、最大5桁ですが、1バイトの BCD では10進数2桁を表現できるため、3バイト区切りで並んでいます。(下位の桁から並んでいます)


まずは10進数にする 16bit データを、1bit づつビットの有無を判定し、ビットがある場合はその「重み」を、BCD で足し合わせます。

テーブルは3バイト区切りですから、足す際には3バイトづつの処理が必要です。


16bit 分を処理すると、BCD 化された数値が出来上がります。

この時点では、4bit で1桁を表現する BCD が、1バイトに2桁分入れられた「パックド BCD」の状態です。


ここから後半。

4bit づつ取り出して、上位 4bit に 03H を付けることで、数値を意味するアスキーコードに変換します。

つまり、目的とする「ゾーン BCD」の状態。


この際、RLD 命令が使用されています。

これも実は BCD を扱うことを考慮して作られた命令で、A レジスタの下位 4bit と (HL) のメモリ内容の間で、データがパックド BCD である前提で 10進数の桁ずらし(ローテート)を行います。


RLD では、Aレジスタの上位 4bit には影響を与えません。

でも、プログラムでは、最後の桁以外は常に上位 4bit に 3 を書き込んでいます。


これは、ループ展開して高速化している代償。大きな高速化のために、ちょっと損してる。

(この部分をうまく処理できると、もっと高速になるかも)


速度の遅い DIV ルーチンは使われていません。

割り算は遅いから逆数を掛ける人が出るかと思って、MUL ルーチンも存在することにしていたけど、それすら不要。


投稿プログラムでは、テーブルの参照アドレスを 16bit 処理している部分と、下位8bit だけで処理している部分がありました。

ここでは、「テーブルは 256バイトの境界をまたがない」ことにして、下位8bit 処理だけに統一しています。




前処理に 33 クロック。


前半処理は、加算が生じる場合 156 クロック。加算が無いと 66クロック。


元データとなる 8086 は、2進数で 0001 1111 1001 0110 。

一番左の 1 の左の桁を処理した際に終わるプログラムになっていますから、14 回処理を繰り返します。

処理される下位 14bit のうち、 1 は 9個、0 は 5個あるので、加算有りが 9回、無しが 5回です。


最後は 5クロック速くなるので、前半処理にかかる時間は


156*9 + 66*5 - 5 = 1729 クロック


後半処理は、前処理として左から「0ではない桁」を見つけ出し、あらかじめ展開されているループの途中にとびこむことで左詰めで文字列を作り出します。


えーと、前処理と本処理でいろいろややこしい関係になるので、ひたすら計算してみると、


5桁243クロック
4桁233クロック
3桁223クロック
2桁213クロック
1桁206クロック

これが必要なクロック数です。

1桁の時だけちょっと特別だけど、それ以降は1桁増えるとクロック数が 10 増えます。


例題では4桁なので、後半は 233クロック。


全部を足すと、


33 + 1729 + 233 = 1995


総計は、1995 クロックとなります。


次ページ: ひたすら引き算


1 2 3 4 次ページ

(ページ作成 2014-08-28)
(最終更新 2014-09-06)
第2版 …他の版 初版

前記事:割り算     戻る
トップページへ

-- share --

2000

-- follow --




- Reverse Link -