3664用最適化事始め&習作「簡易モニタ」

[IMAGE]ディップスイッチが便利

なんとかHOS-H8hは動き出しました。しかし、これまでの作業で、もうちょっ と手を加えられる余地も見えてきました。300Hでありながら300Hでない、いさ さか中途半端な3664の為にもう一手間かけてみます。

そしてもう一つ。HOS-H8hのsample、WEB上で公開されている各種情報の斜め読 み、書店での立ち読み等から、大雑把ですがμITRONプログラミングのカタチ を想像してみました。それを元に、「簡易モニタ」を作成します。

3664はROM/RAMともに容量が小さいので、日立謹製モニタとHOSの共存が 難しい(というか、ソースが手に入らない現状では不可能?な)ため、スタック の伸び具合を見たりするのも気軽には出来ません。ここは一つ、「デバッグや チューンの為にも役立つようなもの」という大きな看板を掲げてしまいましょ う。

2001.02.15記 新井
2001.02.20更新


  1. どんな最適か?
  2. 遂に登場、嘘参六六四
  3. 割ります、割ります、割ります
  4. μITRONプログラムの想像図
  5. Minimum MONITOR…ミニモニ?
  6. 使えるか?ミニモニ

どんな最適か?

さて、「最適化」なんて言うとなんか凄そうな気がしますね。当然これは私の 身の丈に合った、もっと限定的で(普遍に対しての)特殊なものです。一言で述 べると、

「h8300-hms-gcc(以後、単にgcc)に-mh -Sを付けてコンパイルした出力から、 3664用に削れる部分を削る」

ということです。

取り敢えず危険性の少ないと思われる例を挙げてみましょう。

@<aaaa> → @<aaaa>:(16|8)
絶対アドレスです。基本的に@<aaaa>は、@<aaaa>:24と評価されてしま います。

細淵さんのrelaxを賢くするpatchを適用し、周辺I/Oレジスタのアドレス定義 を0xff?? → 0xffff??としておけば、それらに対しては:8になりますが、他は :24のままになってしまいます。

これは悲しい。即値は0xffXXならば:8し、それ以外は:16したいものです。こ れにより命令長/実行ステート数で4/4(:8)、2/2(:16)の節約が出来ます。

@(disp,er?) → @(disp:16,er?)
ディスプレースメント付きレジスタ間接です。これはそのままだと、 @(disp:24,er?)として評価されてしまいます。

無駄ですね。これは4/4の節約になります。デカイ。

これらは機械的にやってしまっても、まず問題は無いと思われます。

もう少し冒険できるなら、というのも考えてみました。

XXXX.l #_label+d,er? → XXXX.w #_label+d,r?
変数や関数のラベル値を、アドレスレジスタとして用いると思われる 32bit拡張レジスタと演算(代入、加算、減算)しています。どうせ上位wordは 無視なので、こうしても構わないでしょう。これで2/2。

しかし、もしアドレスレジスタとしての演算でなかったら…。

ext[us].l er?の削除
配列や構造体へのアクセスのために整数値を拡張していると思われます。 この場合、3664においてこの命令は全く無意味です。2/2の削減が出来ます。

しかし、もしアドレスレジスタとしての演算でなかったら…。

う〜ん。-mhnさえあればこんなことしなくてもいいんですが…。それに、ラベ ルに関わるer?の方はほぼ間違いなくイケそうですが、ext[us].lの方は前後関 係を読まなければならないと思われるので、一段上の対処が必要そうです。

そうそう、どーしてもあとXXX bytes欲しい!そんな時のために少し手間のか かる小技も一つ。

jsr/jmp @<aaaa> → jsr/jmp @@<bb>:8
4/8 → 2/8なので、実行ステート数は稼げませんが、2bytes短くなりま… というのは嘘です。何故なら<aaaa>(word)を0x00<bb>番地に書い ておかなければならないからです。従って、実際の節約byte数は、「(一つの ラベル、<aaaa>に対するjsr/jmpの数)×2-2」bytesとなります。

これを実現するには多少の手間が必要です。C言語プログラムのレベルで2個以 上呼ばれる関数について、

#define <function1> __vfunc1a /* は任意の関数名 */
#define <function2> __vfunc1b
         (略)
#define <function3> __vfunc7f /* 最大は127番まで */
のように規則的な別名を付け、リンカスクリプト内でvector sectionを 割り込みベクタ範囲から更に広げて(最大の場合、sectionの長さ0x0100)、

	SHORT(DEFINED(int18)?ABSOLUTE(int18):ABSOLUTE(_int_default))
	SHORT(DEFINED(int19)?ABSOLUTE(int19):ABSOLUTE(_int_default))
+	SHORT(___vfunc1a)
+	SHORT(___vfunc1b)
	(略)
+	SHORT(___vfunc7f)
        }  > vectors
とします。そして-Sして出力させたアセンブラソースで、「jsr @___vfuncXX → jsr @@YY:8 (但し、YY = XX << 1)」と置換を実行すれば完了です。

余談ですが、この「メモリ間接」についての3664ハードウェアマニュアル(少 なくとも第2版迄)の記述は間違っています。「メモリ上のオペランドはロング ワードサイズで指定します」なんて書いてありますが、きっとそれはアドバン スドモードの話でしょう。

まぁ、これは追い詰められたら使うことにしましょう。


遂に登場、嘘参六六四

基本的な方針は決まりました。後はこれをいかに特別な操作無しに今ま でのmake一発な環境に突っ込むか、です。

とにかく、gccのアセンブラ出力に前述の処理を加える、一種のフィルタプロ グラムを書きます。サクっとrubyとかperlなんかでやればキャッチーなのです が、どちらも不案内なので、ベタベタのCで書きました。ソースは見ない方が 身の為です。

uso3664.c (8612 bytes)
これを適当にコンパイルします。普通のregex libraryがあるCならコンパイル 可能だと思いますが、FreeBSDでしか試してはいません。

frodo% gcc -O -o uso3664 uso3664.c
frodo% strip uso3664
これをMakefileに組み込みます。uso3664は、入出力ファイル名を指定する事 も出来ますが、標準入出力を使うフィルタとしても動作するようにしてありま すので、以下のように書き換えます。

+SOPTMIZ = <uso3664を置いたディレクトリ>/uso3664

-.c.o:
-	${CC}  $< ${CFLAGS} -c
+.c.o:
+	${CC}  $< ${CFLAGS} -S -o - | ${SOPTMIZ} | ${ASM} ${AFLAGS} -o $@

-.s.o:
-	${ASM} $< ${AFLAGS} -o $@
+.s.o:
+	${SOPTMIZ} $< | ${ASM} ${AFLAGS} -o $@
sampleのmakeに使ってみて、その効果を検証してみます。生成バイナリの大き さを調べてみましょう。

h8300-hms-size sample の出力

素のまま:
   text	   data	    bss	    dec	    hex	filename
   9550	      0	   1072	  10622	   297e	sample

sample/Makefileにuso3664を組み込む:
   text	   data	    bss	    dec	    hex	filename
   9358	      0	   1072	  10430	   28be	sample

更にsrc/Makefileにもuso3664を組み込む:
   text	   data	    bss	    dec	    hex	filename
   9002	      0	   1072	  10074	   275a	sample
おお!548bytesも削減できました。笑わないで下さい。3664の限界に挑戦した システムを作る時、32KBが33.6KBになるとしたら…まさに血の1.6KBになるで しょう…。そうウマくいくとは限りませんが、アセンブラに走るよりは低コス トで多少の効果は期待できます。


割ります、割ります、割ります

こうしてみると、更に省サイズ化したくなってきました。以前から課題として いたsrc/以下のファイル分割に手を着ける時が来たようです。

方針は、「一関数一ファイル」。これだけです。itron.hから インクルードされるヘッダだけでは定義されないモジュール内部だけの関数、 例えばeventflg.c内の__chk_flg()とか、task.c内の__rel_stp()があるので、 それらのために内部的なヘッダファイルを書く必要はありますが。

ファイル数が激増するので、src/task.cなるtask.cは分割して、 src_div/task/<関数名>.cとします。src_div/objも作っておけば、.oの 海に溺れることもありません。

そのように作業し、Makefileを書き換えたものが、

hos-h8-v0.07-gnu-src_div.tar.gz (14014 bytes)
です。src/と同じディレクトリで展開して使います。

それでは、このsrc_divにもuso3664を適用してsampleをmakeしてみましょう。結果 は…、

   text	   data	    bss	    dec	    hex	filename
   5648	      0	   1072	   6720	   1a40	sample
おお!使っているサービスコールが少ないせいか、とんでもなく小さくなった ようです。素のままに比べ、40%も小さくなりました。

あ、勿論、sampleとしては(多分)正常動作していますよ。念の為。


μITRONプログラムの想像

こんな事を書いて良いのだろうか?ちょっとこのセクションは書いていて怖い。 自分の無知を世に曝してるよ〜な気がしないでもありません。まぁ、それは 「今更」なので、気にせず行ってみましょう。

図に描くとこう。

[IMAGE]μITRONプログラム想像図 こんな感じ?

左: タスクの構造想像図 右: 非タスクな割り込みハンドラ想像図
結局の所、タスクってのは、メッセージ/フラグ/セマフォを待ったり送ったり してループするか終了する構造が基本のようです。デバイスドライバなんかも タスクで作るもののよう。

んで、メッセージ/フラグ/セマフォでタスク間の同期をとったり、データのや り取りをしたりすると。

割り込みハンドラに関しては非タスクですが、これまたフラグ等を通じて連係 をとれるので、タスクとともに協調動作を実現できます。

何かを監視するタスクとか、何かを動かすタスクとか、外部からの要求に応じ たシステムの行動方針を決めるタスクとか、そんなタスクの集合体がアプリケー ションになるということでしょうか。

そういうスタイルだと、並列に複数の処理を行なうようなプログラムであって も相互の連係作業はOSまかせで済み、タスクの記述はとてもシンプルになり、 開発効率も上がるということのようです。

他にも「リアルタイム」と名乗るには、応答時間がどうこうとかいうのもある らしいですが、性能を保証しなければならないという話でもないので、気にす るのはよしておきます。

まぁこんなところがHOS-H8歴1ヶ月ちょっとの認識です。ここから更に一歩進 み出す為にも、絵に描いたような構造で習作モニタを作ってみます。


Minimum MONITOR…ミニモニ?

基本的に必要そうな機能を考えます。まずは低レベルな部分。

文字列出力
モニタなので、当然UIを備えなければなりません。この場合SCI3。232Cで 繋いだホストに結果を出力する機能が必要です。
文字列入力
そしてその対偶として、入力する機能も必須となります。ホストからの入 力を受け付けます。エコーがあって、BSくらいは使えた方が良いでしょう。
これだけ。とにかくSCI3でホストと文字をやり取りできれば可とします。次は モニタ本体です。
現在時刻表示
システム時刻を表示します。まぁ別に役に立つとも思えませんが、簡単に 書けそうなので。
メモリダンプ
リンカの出したマップファイル片手に、静的変数、スタックの伸び具合な んかを見るのに役立ちます。また、当然各種I/Oレジスタの監視も出来ます。
メモリ書き込み
これまたマップファイル片手に、静的変数をいぢったり(危険 ^^;)周辺 I/Oをいぢったりするのに役立ちましょう。
イベントフラグ状態表示
メモリダンプが出来れば必要無いとも言えなくはないですが、一発で見ら れるのは良いかも。
メイルボックス状態表示
これも同様。
タスク状態表示
これも。
メイル待ち
ちょっと??な感じもしないではないですが、イベントの発生を目で確認 出来たらなぁ…なんて。
フラグ待ち
同じく。
セマフォ待ち
同じく
こんなところでしょう。これをタスクの形で書きます。勿論、そういう練習と いう意味もありますが、実際に役立つツールとして考えた場合も、その方が都 合が良いからです。

即ち、アプリケーション開発の際、これらのタスクを組み込んでしまえ ば、そのまま確保すべきスタック容量の見極めとか、デバッグに使える(とい いなぁ…位かもしれませんが)からです。こういうことが出来るのも、OSたる HOSを利用する利点なのでしょう。

「Minimum MONITOR」(ミニモニ^^;)の構造を描いていきます。

基本的にはSCI3を使って文字列入出力を受け持つ、「コンソールドライバタス ク」と、受け取ったコマンドを解析して実行、結果を作成する「コマンドシェ ルタスク」の2つのみです。

[IMAGE]コンソールドライバタスクの図
↑: コンソールドライバタスク ↓: SCI3割り込みハンドラ(非タスク)
[IMAGE]SCI3割り込みハンドラの図

「コンソールドライバタスク」は、他タスクからの要求(mail)を待ち、要求が 来たらそれに応じた処理をして待ちに戻るループで成っています。文字送信/ 受信とも割り込みを使い、タスク内部ではSCI3の実際の読み書きは行なわず、 それぞれのバッファ(に積む|から取る)だけです。(送信バッファに積む|一行 読み込み開始の)際に、それぞれの割り込みを許可し、タスクとは別の割り込 み処理の方で、バッファのデータを実際にSCI3に対して読み書きし、(バッファ が空に|一行入力終了に)なればその割り込みを終了します。割り込み処理との 連係はイベントフラグを通じて行ないます。

[IMAGE]コマンドシェルタスクの図
コマンドシェルタスクの図
「コマンドシェルタスク」は、「コンソールドライバタスク」に対して初期化 の要求を行った後、一行入力を得、得られた文字列に従って処理を行なって、 また一行入力待ちに戻るループです。プログラムを簡単にするため、コマンド は1文字の数字とし、受け付けるオペランドも16進表記のunsigned int(16bit) のみとします。

そうして出来たのが、

mini-moni-v0.01.tar.gz (11898 bytes)
です。h8300-hms-sizeの出力は、
   text	   data	    bss	    dec	    hex	filename
   9570	     22	    776	  10368	   2880	m0
となり、既にHOS本体が含まれている事を考えれば十分に小さいものが出来た のではないかと思います。


使えるか?ミニモニ

それでは、ミニモニを使ってみます。


Minimum Monitor V0.01 (on HOS-H8h v0.07)

% 
0		 : Display TIME
1 XXXX XXXX	 : Dump           #START #END
2 XXXX		 : BYTE write          #START
3		 : Event flag status
4		 : MailBOX status
5		 : TASK status
6 XXXX		 : Wait mail              #ID
7 XXXX XXXX	 : Wait flag         #ID #PTN
8 XXXX		 : Wait semaphor          #ID
ささやかな起動メッセージとヘルプです。ヘルプは、不正なコマンドや空の改 行で出力されます。

% 0
0001D75D
システム時刻の表示です。やっぱりあまり意味がありません。せめて10進で日 時分秒の表示にすれば?まぁいいでしょう。

% 1 ffa0 fff0
FFA0 : 00 10 FF FF 00 E2 13 DF 00 19 B0 30 00 0D FF F3  | ...........0....
FFB0 : 00 00 00 00 00 00 00 00 00 7E FF FF FF FF FF FF  | .........~......
FFC0 : AA 00 FF FF 01 00 01 00 80 3F FF FF F3 5F FF FF  | .........?..._..
FFD0 : 08 00 FF FF 08 FF FF FF 3F FF 8F 01 FF FE FF FF  | ........?.......
FFE0 : 0E 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF  | ................
FFF0 : 00 00 70 C0 50 FF 30 FF C0 00 FF FF FC FF FF FF  | ..p.P.0.........
メモリダンプでI/O領域を表示してみました。
% 2 ffd4
FFD4 : 08 > ff
FFD5 : FF > 
% 
1byte書き込みです。空の改行を入れるか、不正な行でプロンプトへ戻ります。 現在でもデバッグ用のLEDがport10に接続されているので、0xffd4番地への書 き込みで点灯、消灯の確認が出来ました。(PMR1/PCR1はミニモニでPORT1出力 に初期化されている)

% 1 f8c0 f920
F8C0 : 00 00 00 00 00 00 F8 DE 00 00 00 08 00 00 00 08  | ................
F8D0 : 00 00 00 00 F8 EC 00 00 00 00 00 00 00 00 00 00  | ................
F8E0 : F9 70 1D EE 00 00 FA 78 00 00 F9 50 00 00 F9 02  | .p.....x...P....
F8F0 : 1C 40 00 00 00 00 00 00 00 00 00 01 00 00 00 00  | .@..............
F900 : F9 50 00 00 F9 0C 0E 54 00 00 F9 E0 00 00 0D FA  | .P.....T........
F910 : 1E AC 00 01 00 00 00 00 00 00 00 00 00 00 00 00  | ................
F920 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
スタックを観察してみました。スタックのアドレスはマップファイルで調べら れます。↓から、Stack2が0xf8c0〜0xf920に存在することが分ります。

                (略)
 COMMON         0x0000f8b0      0x1d4 cfg_c.o
                0x0000f8b0                mcbtbl
                0x0000f8c0                Stack2
                0x0000f920                scbtbl
                0x0000f930                msgbuf0
                0x0000f950                tcbtbl
                0x0000f990                spcbtbl
                0x0000f99c                StackPool1
                0x0000f9a0                Stack1
                0x0000fa20                rdyque
                0x0000fa60                fcbtbl
                (略)
タスクのスタック領域はbssに確保され、bssはスタートアップで0に初期化されるので、汚れ具合からスタックがどの程度まで伸びるのかが分ります。

これは既に減量されているので、さほど無駄にはなっていません。

% 3
Flag ID : Ex.inf.  Wait task  Flag pattern
0001      0000      0000      0000
0002      0000      0000      0000
0003      0000      0000      0000
フラグ状態の一覧です。FID_SCI3_RX,FID_SCI3_TX,FID_Consoleの状態が見ら れました。

% 4
MBOX ID : Ex.inf.  Wait task  Packet addr.
0001      0000      0000      F9E0
メイルボックスの状態です。

% 5
TASK ID : Ex.inf.  Priority   Status
0001      0000      0001      TTS_RUN 
0002      0000      0002      TTS_RDY 
タスク状態の一覧です。あれ?ConsoleタスクがRDYですね…多分、Console内 のset_flgでディスパッチが起こって、ループの先頭のwai_msgまで辿り着いて いないからでしょう。こんな風に、多少は流れを掴む助けになるかな?

次こそは本当に何かSCI3以外のデバイスを…。