August 2008 Archives

オリンピック閉幕で毎度の韓国祭り

| No Comments | No TrackBacks
オリンピックの閉会式をBBCのテレビとNHKの両方で同時に見ていました。
BBCは臨時チャネル(デジタル301)のせいか、ほとんど解説もなく淡々と映像を流しているだけなのですが、NHKの方は適宜解説が入ってわかりやすいからです。

ただ、NHKはロケーションフリーTVでの受信なので画質がよくないので、映像はBBCのテレビで見ていました。

関係ないですが、ロケーションフリーTVの映像はBBCから0.5秒と遅れずに出ます。映像ソースはいわゆる「代表撮影」なので全く同じで、日本とイギリスに到達するのもほぼ同じタイミングでしょうから、あとは日本で受信したものをmpeg4圧縮してインターネットで転送して展開する時間ということになると思いますが、かなり高速ですね。

で、その閉会式中。CGの世界地図が出てきて、そこに"Sea of Japan"と書いてあるのがあり、ちょっと気にはなっていたのですが、やはりというかなんと言うか、騒ぎになっているようですね。開会式でも同様なことがあったそうで。

このあたりはググるといろいろ出てきますが、こちらがなかなか詳しくまとまっています。
要は韓国がお約束のように反発しるというわけです。

なんというか、あまりにお約束過ぎて脱力...。いい加減少しは大人になってほしいものです。

ついでに映像中に出た、世界中から北京に延びる線も日本からは3本あるのに対し、朝鮮半島からは1本もありませんでした。ひょっとしてネタなのか?

ちなみに、その日ロンドンではバッキンガム宮殿前でコンサートが開かれるなど、馬鹿騒ぎでした。うちの前もバンド演奏しながら走るトラックが行き交ってうるさいったら。私はひょっとすると2012年をロンドンで迎えることになるでしょう。どうなることやら。





ストーンヘンジ

| No Comments | No TrackBacks
車が手に入ったので、ストーンヘンジに行ってきました。
ロンドンからは1時間半ほどで、幹線道路を走っていると突然目の前に現れます。もっと入り組んだ所を走ってたどり着くのではないかと考えていたのでちょっと驚きました。

Photo-0083.jpg夏休みのせいか、観光客がひっきりなしでした。
ちなみに入場料は6.5ポンド、オーディオガイド付きです。
オーディオには日本語もあります。今回は二人いたので日本語と英語の両方を借りました。
比べてみると、日本語は英語の直訳ですが、それなりに苦労したのか、わかりやすい説明にはなっています。

Photo-0078.jpg
風が強くて結構肌寒い日でした。私は薄手のコートを着ていきましたが、ちょうど良かったくらいです。


久々にオークション

| No Comments | No TrackBacks
とうとう、mkIIを落札してしまいました...
実際に入手できるのは、次回の日本出張時なので9月中旬になりますが.

なんだか深みにはまっていくような...これでよかったのだろうか....

Z80のC言語クロスコンパイル(SDCC)(5)

| No Comments | No TrackBacks
演算ライブラリについて
sdccには基本的な標準ライブラリが入っています。 その中には

stdio.h
stddef.h
stdlib.h
string.h  
など、C言語として当たり前のものもあるのですが、その他に、基本的な演算や浮動小数点なども含まれます。
たとえば、整数の掛け算や割り算をする場合も、関連するライブラリがリンク時に埋め込まれます。z80は標準で掛け算や割り算の命令を持たないので、これは仕方のないところです。

ただ、演算ライブラリは関数単位ではなくオブジェクト単位で埋め込まれるので、不要なものまで入ってしまうことがあります。

演算ライブラリのリンク例

たとえば、次のような簡単なプログラムをコンパイルしてみましょう。
-- test.c
long mul(long a, long b) {
  return a * b;
}
void main () {
  mul(654321, 123456);
}
-- 
今回のプログラムは試しやすいようにアーカイブを用意しました。

sdcc5.zip (sdccとMakeのできる環境(UNIX系のOSやCygwinなど)が必要です)

Makefileが入っていますのでmakeのみでコンパイル可能です。 まずは、アーカイブのsample1ディレクトリでmakeしてみてください。

Makefileにはいろいろ書いてありますが、要は次のようにしているのと同じです。

% sdcc -mz80 --code-loc 0 --no-std-crt0 test.c  
今回は話を簡単にするため、crt0は使っていません。

さて、これでオブジェクトのてtest.ihxのほか、マッピングファイルのtest.mapが出力されていると思います。
mapファイルは、グローバルシンボルがどこに配置されているか、各エリアのサイズがどのくらいかが示されています。

そのmapファイルを見てみましょう。

-- test.map(抜粋)
(中略)
Area                               Addr   Size   Decimal Bytes (Attributes)
--------------------------------   ----   ----   ------- ----- ------------
_CODE                              0000   090D =   2317. bytes (REL,CON)

      Value  Global
   --------  --------------------------------
     0000    _mul
     0000    _mul_start
     0032    _main
     0032    _main_start
     0032    _mul_end
     004A    __mullong_rrf_s
     004A    __mullong_rrx_s
     004A    _main_end
     004D    __modslong_rrf_s
     004D    __modslong_rrx_s
     0050    __modulong_rrf_s
     0050    __modulong_rrx_s
     0053    __divslong_rrf_s
     0053    __divslong_rrx_s
     0056    __divulong_rrf_s
     0056    __divulong_rrx_s
     0059    __mulint_rrf_s
     005F    __divsint_rrf_s
     0065    __divuint_rrf_s
     006B    __mulschar_rrf_s
     0071    __divschar_rrf_s
     0077    __muluchar_rrf_s
     007D    __divuchar_rrf_s
     0083    __modschar_rrf_s
     0089    __moduchar_rrf_s
     008F    __modsint_rrf_s
     0095    __moduint_rrf_s
     009B    __rrulong_rrf_s
     00A1    __rrslong_rrf_s
     00A7    __rlulong_rrf_s
     00AD    __rlslong_rrf_s
     00B3    __divschar_rrx_s
     00BA    __divschar_rrx_hds
     00C1    __modschar_rrx_s
     00C8    __modschar_rrx_hds
     00CF    __divsint_rrx_s
     00DB    __divsint_rrx_hds
     00E3    __modsint_rrx_s
     00EF    __modsint_rrx_hds
     00F7    __divuchar_rrx_s
     00FE    __divuchar_rrx_hds
     0105    __moduchar_rrx_s
     010C    __moduchar_rrx_hds
     0113    __divuint_rrx_s
     011F    __divuint_rrx_hds
     0127    __moduint_rrx_s
     0133    __moduint_rrx_hds
     013B    .div8
     013B    .mod8
     0143    .div16
     0143    .mod16
     0180    .divu8
     0180    .modu8
     0183    .divu16
     0183    .modu16
     01B8    __rrulong_rrx_s
     01D5    __rrslong_rrx_s
     01F2    __rlslong_rrx_s
     01F2    __rlulong_rrx_s
     020F    __modslong
     020F    __modslong_start
     02E8    __modslong_end
     02E8    __modulong
     02E8    __modulong_start
     03FF    __divslong
     03FF    __divslong_start
     03FF    __modulong_end
     04E1    __divslong_end
     04E1    __muluchar_rrx_s
     04F4    __mulschar_rrx_s
     04FB    __mulschar_rrx_hds
     0507    __mulint_rrx_s
     0513    __mulint_rrx_hds
     0513    __muluchar_rrx_hds
     052C    __divulong
     052C    __divulong_start
     0610    __divulong_end
     0610    __mullong
     0610    __mullong_start
     090D    __mullong_end
-- 
いかがでしょうか。test.c本体は0x0000~0x0049に配置されています。staticスコープではない関数はstartとendのシンボルが付加されます。_main_endなどの終了位置は終了アドレス+1を示します。

そして、0x004a~0x090cはすべて演算用モジュールです。見ると、divだのmodだの、関係ないものまでたくさん入っていて、そのサイズは実に2,243バイトに達します。

では、もし標準ライブラリをまったくリンクしないとどうなるでしょう。

% sdcc -mz80 --code-loc 0 --no-std-crt0 --nostdlib test.c
?ASlink-Warning-Undefined Global '__mullong_rrx_s' referenced by module 'test'
こうなると思います。つまり、掛け算で'__mullong_rrx_s'という関数を呼び出しているわけです。

この関数が定義されているのは、 SDCC/lib/z80/stubs.o というオブジェクトなのですが、実はこのオブジェクトで演算の本体が実装されているわけではありません。名前のとおりこのオブジェクトはただのスタブで、演算に関するありとあらゆるオブジェクトを参照するようになっています。

リンカはオブジェクト単位ですべての参照を解決しないと出力を生成しないため、スタブが参照しているオブジェクトをすべて埋め込む結果となってしまいます。

オブジェクトを追いかけてみる

ライブラリがリンクするオブジェクトを追いかけるためには、オブジェクトファイルの構造を知ることが必要ですが、ここでは最低限知っておけばいいことだけ挙げます。
先ほどのstubs.oを見ると、次のようになっています。

-- stubs.o (抜粋)
(中略)
S __muluchar_rrx_s Ref0000
S __mullong Ref0000
(中略)
S __rrulong_rrf_s Def0051
S __mullong_rrx_s Def0000
(中略)
-- 
最初の方にこのような"S"で始まる行がたくさんあります。これはシンボル定義で、シンボル名と参照・定義の種別を表しています。Refとあるのは他のオブジェクトで定義されているシンボルの参照、Defとあるのがこのオブジェクトで定義されているシンボルと定義されているアドレスです。

リンカはリンクしようとしたオブジェクトにRefで示されるシンボルがあると、それに対応するDefのシンボルが定義されているオブジェクトをリンクします。stubs.oは大量のRefがあるため、それに対応するDefのあるオブジェクトをすべてリンクするまではリンクが終了しません。

ソースを追いかけてみる

この問題を解決するためには、オブジェクトファイルを直接いじる手もあるのですが、幸いsdccにはソースがついているので、ソースを直接インポートしてしまうのが手っ取り早いでしょう。

ソースは、 SDCC/lib/src/z80/ に入っています。stubs.s を見ると、以下のようになっています。

-- stubs.s (抜粋)
__mullong_rrx_s::
__mullong_rrf_s::
        jp      __mullong
--   
何のことはない、__mullongに飛んでいるだけです。__mullongは

SDCC/lib/src/__mullong.c
に入っています。
そこで、stubs.sをコピーして、必要なmullong_rrx_s以外の定義を削除してしまいます。次に__mullong.cを個別にコンパイルしてリンクします。すると次は、

?ASlink-Warning-Undefined Global '__muluchar_rrx_s' referenced by module '_mullong'
というリンクエラーが出ます。この定義は

SDCC/lib/src/z80/mul.s
に入っているもっとも基本のライブラリなので、これもアセンブルするようにします。

これらを済ませたものがアーカイブのsample2ディレクトリに入っています。
このディレクトリでmakeしてみてください。リンクエラーは出なくなったと思います。

結果生成されたmapファイルを見てみると、

-- test.map(抜粋)
Area                               Addr   Size   Decimal Bytes (Attributes)
--------------------------------   ----   ----   ------- ----- ------------
_CODE                              0000   0395 =    917. bytes (REL,CON)

      Value  Global
   --------  --------------------------------
     0000    _mul
     0000    _mul_start
     0032    _main
     0032    _main_start
     0032    _mul_end
     004A    __mullong
     004A    __mullong_start
     004A    _main_end
     0347    __mullong_end
     0347    __mullong_rrf_s
     0347    __mullong_rrx_s
     034A    __muluchar_rrx_s
     035D    __mulschar_rrx_s
     0364    __mulschar_rrx_hds
     0370    __mulint_rrx_s
     037C    __mulint_rrx_hds
     037C    __muluchar_rrx_hds
-- 
今度はリンクされる関数がかなり少なくなりました。リンクされたライブラリの大きさは818バイトです。

__mullong.cを最適化したり、mul.sで使われていないルーチンを削ったりすることでもっとサイズを削減することも可能です。
まぁ、そのような場合は計算用ライブラリを個別に用意して、それを呼び出した方がいいかもしれません。

ちなみに、SDOSでは、以下については標準ライブラリは使わず、アセンブリ言語で独自の実装を行っています。

  • 乗算(16ビット x 16ビット = 32ビット)
  • 除算(32ビット / 8ビット = 32ビット)
どちらが絶対的によいと言うわけではないのですが、16ビットまでの大部分の演算はライブラリを呼び出さずにインライン展開されるようなので、必要な演算の種類やメモリ・速度の制約などのトレードオフを勘案して使い分ければいいと思います。

ロンドンのバナナ事情と喫煙事情

| No Comments | No TrackBacks
私はロンドンに住んでいますが、町中にバナナの皮が落ちているという話は寡聞にして聞いたことがありませんし、自分で見たこともありません。

もっとも、公共の屋内で完全禁煙のせいか、タバコの吸殻は非常に多いです。
この「完全」がなかなかやっかいで、たとえば分煙(喫煙ルームを設けるなど)もだめ。パブも屋内は禁煙。なので、気の利いた店はテラス席などを用意することを怠りません。場所柄テラス席を用意できない場合でも、入り口前に灰皿を設置することが多いです。

そのため、屋外席の方が屋内よりも早くいっぱいになってしまうことも珍しくありませんし、入り口の外まで出て喫煙しつつビールを飲んでいる人もたくさんいます。

最近は大陸ヨーロッパにもこの波が広がっていて、たとえばドイツも今年の2月くらいから同じような法律が施行されたそうです。先日ドイツに行きましたが、唯一喫煙ルームを見かけたのはフランクフルトの空港内でした。

いま、PC-6001のPSGでPCM再生を試みています。
全体的にかなりいい感じで進んではいるのですが、クロック計算がなかなか合わないというのが困りものです。

カタログ上のPC-6001のクロック周波数は3,993,600Hzです。半端に見えますが、因数分解すると
3993600 = 2^12 * 5^2 * 3 * 13
となります。カセット入出力やRS-232Cなどで作られている1,200Hzの2の累乗倍で考えると、
3993600 = 1200 * 256 * 13
となり、4MHzのZ80のスペックに従いつつかなり切りのいい数字なんですね。

さて、PCM再生には正確なタイミングが不可欠です。これまでの実験で、とりあえずCD音質の44,1KHzは不可能ではないのですが、S/N比が悪くバランスが悪いことが判りました。なにせPSGの音量コントロールだと、4bitでの再生になりますからね。

その1/4の11.025KHzだと、音量に12bitを割くことができ、音質的にもかなりいい感じです。
ああ、この辺は説明してませんね。PSGを3チャンネル使うことにより可能になるんですが、私も理解が追いついていないのでこれはまた後ほどということで。

とりあえずこれで実験を続けてみました。計算だと、

3993600/11025 ~= 362.23

となります。まぁこの際小数点以下は無視するとして、3チャンネルのPSG音量をセットアップするのに362T(-state)かけられる、ということです。もちろん、外部影響を無視するため、VDGからのDMAはoffにし、割り込みも止めています。

ところが、これで再生すると、遅いんです。当然音程も低いんです。そりゃもう、一青窈が平井堅になるほどに(嘘)。

処理には余裕があるので、16Tほど削減してみました。すなわち346Tです。すると、まぁいい感じです。あ、今回はM1サイクルのT-stateをちゃんとカウントしています。
でも、完璧じゃないと言うか、やっぱり少しはずれるんです。大体、削減した16Tというのも適当に削減するウェイト用の命令を選んだだけで、なんら根拠はありません。
.......これは何かがあるに違いない、とは思うのですが、確実な原因はわからないのです。

いくつか気になることは以下のとおりです。
  • CPUの周波数が3993600Hzなのに対し、PSG(AY-3-8910)の周波数は3578545Hzです。そしてこれはMSXのCPU周波数と同じです。もっともPSGにクロックいっぱいいっぱいで出力を与えられるわけはないのですが。
  • 仮に周波数をPSGの持つ3578545Hzとして計算すると、362Tで出る周波数は約9885Hz、修正後の346Tで出る周波数は10343Hzです。
  • MSX版でPSG再生するプログラムを参考にしたのですが、そこでは
    out (c), r
    にかかるステートがなぜか15となっていました(M1 wait込み)。Z80の仕様上は12です。M1ウェイトを入れても13です。
    そして、そのMSXのCPU周波数はPC-6001のPSGと同じ、3578545Hzなのです。
どなたか教えて、Z80 Geekな人!

#注記
上記の記事を書いているときは、実は再生時にdiを忘れると言う大ポカをやってました。なので、そのときのタイミングはかなり当てになりませんでした。ただし、割り込みを禁止して実験してみたところ、やはり計算どおりには行かず、平井堅です。どうしたものでしょうね??

SDカードドライバの設計(5)

| No Comments | No TrackBacks
プリンタポートの場合

カードリーダをプリンタポートに接続する場合のコードを考えてみました。昔のパソコンの大部分は入力がBUSY信号のみですが、幸いなことにSPIで必要とする入力信号は1本なので、これで間に合います。

PC-6001のプリンタ関連のポートは以下のとおりです。
	0x91	data output
	0xc0	busy(bit1)
  
なお、回路では出力の際、インバータを通しているので、ビットは反転出力する必要があります。

では、早速コードを見てみましょう。例により、レジスタEを出力としています。
-- 
	ld	d, #0xff
_main:
	ld	a, d
	out	(#0x91), a
	dec	a
	out	(#0x91), a
	in	a, (#0xc0)
	add	a, d
	rl	e
	; _mainからを8回繰り返す
	ret
-- 
  
ここではハードウェアの設定として、以下を仮定しています。
  • CLKはビット0とします。
  • DIはビット1~7のどこでもかまいません。
ここでのトリックは、

	add	a, d
です。ポート0xc0からの入力は、ビット1以外は常に0なので、これで入力がある(a=0x02)場合は0xffを足すとキャリーが立ちます。しかも、レジスタDを使いまわせるので、利用するレジスタがA, F, D, Eの4つのみです。

結構これは重要で、このルーチンの外側でループを使ってバッファに書き込みを行うので、

	ld	(hl), e
  
のようにバッファへのポインタ(この場合HL)とループカウンタを利用します。ジョイスティックの場合はバイト入力でAFDEHLを使ってしまい、残りのレジスタは2つ。ループカウンタ用のレジスタをBにしてdjnzでまわしても、ぎりぎりレジスタが足りないので、実は裏レジスタを利用しています。惜しい...。

プリンタポートだとその必要がないのでexxと裏表のレジスタ転送の分が浮きます。バイトごとにこの処理が必要なので、1セクタあたり12T*512回分。合計6,144Tで、意外に馬鹿になりません。

もし、SDカードからの入力信号が1のときにポート0xc0の値が0になるようであれば、上記の代わりに

	ld	c, #0x02
を準備しておいて、

	sub	c
とするとよいでしょう。

T-stateは1bitあたり53、1バイトで441となります。裏レジスタを利用しなくて済む分も考慮して、1セクタあたりの時間は約260,000T、期待できる転送レートは3kB/s(VDG on)または6kB/s(VDG off)となります。前回よりさらに1.5倍のスピードになりますね。

なお、これは現在のSDカードアダプタを、ケーブル配線のみ変更すれば適用可能です。ただ、テストはしていませんのでご注意ください。

SDカードドライバの設計(4)

| 5 Comments | No TrackBacks
高速読み出しの工夫
第1回で説明したように、ジョイスティックポートからのデータ入力はかなり手間がかかります。

高速化は最もたくさん実行する部分から行うのが定石なので、心臓部であるバイト入力をする部分を最適化するのが最も効果的です。
(ポート0xa0)<-0x0f	レジスタ0x0fをラッチ
(ポート0xa1)<-0x10	DIを書き込み
(ポート0xa1)<-0x11	クロックを反転
(ポート0xa0)<-0x0e	レジスタ0x0eをラッチ
A<-(ポート0xa2)	データ読みこみ、ビット0にデータが入る
    
これで1ビット。1バイト入力するにも8倍かかります。これを限界まで高速にする挑戦をします。

最初のコード

まず、Z80の入出力は

in	a, (n)
out	(n), a
    
がそれぞれ11 T-state(以下11Tと表記します)で最速ですので、これを使います。
同じポートから連続してバイト列を読み取りたい場合などは
ini
inir
outi
otir
でブロック転送するのがいいのですが、ここでは残念ながら適しません。
入力結果はレジスタeに入れると仮定します。
-- 
	ld	e, #0x00	; 結果
	ld	b, #0x08	; ループカウンタ
_loop:
	sla	e
	ld	a, #0x0f
	out	(#0xa0), a
	ld	a, #0x10
	out	(#0xa1), a
	ld	a, #0x11
	out	(#0xa1), a
	ld	a, #0x0e
	out	(#0xa0), a
	in	a, (#0xa2)
	and	a, #0x01
	jr	nz, _zero
	set	0, e
_zero:
	djnz	_loop
	ret
-- 
  
最初は上記のような感じでした。実際には入力ポートの判別を動的にやっていたので、もっと複雑だったのですが。
上記のコードのT-stateを計算すると、1bitあたり123T~126Tで、1バイトあたり1,003T~1,027Tとなります。

PC-6001のM1サイクルにはウェイトがあるらしいです。
海外のMSX関連の資料(たとえばこことか)を見たところ、やはりM1に1サイクル追加されるようです。よって、すべてのオペレーションのT-stateはZ80の仕様のドキュメントにあるT-stateから1増加させた数値になります。おそらくPC-6001も同じだと思われます。
ただし、この記事の中の数値はM1ウェイトを入れていない数値です。
これにセクタリード用のコマンド送信やらループ管理やらを入れて、1セクタ(512バイト)のリードにかかる時間はおよそ580,000T、実効速度2MHzとすると290msecということになります。実際にはさらにFATの管理コードの実行時間があるので、もっとかかります。

最適化

ここで、時間が短縮できるものがあります。それが以下の3種類です。
  1. Aレジスタのセットアップ
    上記のソースでは、いちいちAレジスタに即値代入をしていましたが、これは7Tかかります。あらかじめ別のレジスタに値を準備しておけばレジスタ間転送で4Tで済みます。
  2. Eレジスタの値の代入
    最初はandを使ってゼロフラグ経由で入力を判断していました。しかしうまいことに、入力がアキュームレータの第0ビットに入ってくるので、4Tのrrca一発でキャリーフラグに結果を追い出せます。また、8回シフトするのでEレジスタへ最初の0代入は実は不要です。
  3. ループ
    ループを展開すればサイズは大きくなりますが、djnzの分の時間は浮きますね。
というわけで、次のようなコードになりました。
-- 
	ld	hl, #0x0e0f
	ld	d, #0x10
_main:
	ld	a, l
	out	(#0xa0), a
	ld	a, d
	out	(#0xa1), a
	inc	a
 	out	(#0xa1), a
	ld	a, h
	out	(#0xa0), a
	in	a, (#0xa2)
	rrca
	rl	e
	; _mainからを8回繰り返す
	ret
-- 
  
inでデータを読み取ったあと、rrcaでビット0をキャリーに追い出して、それをeのビット0から入れています。
これでT-stateは1bitあたり83、1バイトあたり691と、最初のコードの70%ほどになります。さらに外部でも工夫して、1セクタあたり約390,000T、2MHzで200msec以下となりました。

計算上ではなくて実測してみると、およそ2kB/s出ているので、そちらから考えれば1セクタ250msです。FAT管理などのオーバヘッドを考えるとおよそ妥当なところでしょう。

なお、よく知られているようにVDGからのBUSREQを止めると、画面は乱れますが速度は約2倍、約4kB/sとなります。

ちなみに、バイト出力はもっと高速です。inする必要がなく、レジスタの再ラッチが不要なためです。

もしプリンタポートで入出力したとすれば、やはり同じ理由で入出力が少し高速になるでしょう。ただし、プリンタポートには電源が来ていないので、ジョイスティックポートよりも物理的な接続が面倒になります。

これがバイト入力に関して私が考え付いた限界です。もし上記でもっと最適化、高速化できる方法があったら教えてください。


Z80のC言語クロスコンパイル(SDCC)(4)

| No Comments | No TrackBacks
インラインアセンブルコード
sdccのCコンパイラの出力コードの効率があまりりよくないことはお話しました。 そのため、インラインアセンブルを使うことも多いでしょう。 インラインアセンブルの利用は、他のコンパイラ同様、以下のような利点があります。
  • 特定の部分のみ高速化できます。
  • Cプリプロセッサが働くので、#defineの定義が利用できます。
インラインアセンブルコードは、関数内に__asm~__endasm;で囲んで書きます。例えば、次のようになります。
-- 
static char *message;

void func() {
  // 他のコード...

__asm
	ld	hl, #_message
	call	_show_message
_endasm;

  // 他のコード...
}
-- 
  
この例では、アセンブルコードからCコードのmessageを参照しています。 ローカル変数の参照も出来ないことはないのですが、第2回で説明したように、スタック上の位置が宣言の順番に依存するので、避けた方がいいと思います。

警告の抑制

インラインアセンブラを使ってコードを書くと、

myprog.c:10: warning 85: in function myfunc unreferenced function argument : 'arg'

という警告が出てしまうことがあります。これは、関数の引数をアセンブルコード内のみで利用し、Cコード内で参照しないためです。sdccは__asm~__endasm;で囲まれた部分はそのままアセンブラに渡し、一切検査しません。

この警告が気になるなら、#pragmaで抑制することができます。
-- 
#pragma save
#pragma disable_warning 85
void func(char* arg) {
__asm
  // アセンブルコード...
__endasm;
}
#pragma restore
-- 
  
#pragma save
でこれまでのpragma情報をいったん保存し、
#pragma disable_warning 85
でwarning 85の出力を抑制します。そのままだとこれ以降ずっとwarning 85が出なくなってしまうので、関数終了時に
#pragma restore
でpragma情報を元に戻します。

naked関数

アセンブルコードで関数本体を書いても、第2回で説明したようなixのバックアップ~リストアは出力されます。場合によってはこれが不都合なこともあります。そのため、__nakedという予約語があります。この予約語がついた関数は、一切のプリアンブルコードが出力されません。retすらしません。そのため、全て自分で書く必要があります。
-- 
void func(char* arg) __naked {
__asm
  // アセンブルコード...
  // retも自分ですること
__endasm;
}
-- 
  
インラインアセンブラTips: 関数ポインタの定数化
sdccでは、コードセグメントにおける関数ポインタの定数化ができません。 たとえば、以下のようなコードがあったとします。
-- 
char func1(int x) { ... }
char func2(int x) { ... }
char func3(int x) { ... }

const char (*functions[])(int) = {
  func1, func2, func3
};
-- 
  
ここでfunctionsは定数ですので、コードセグメント内に配置して欲しいところなのですが、sdccはfunctionsをデータエリアに置き、メンバの初期化をGSINIT内に出力します。そのため、非常に効率の悪いコードになるだけでなく、本来の意味での定数ではなくなっています。

これを、naked関数を使って解決する手段があります。 次のように書きます。
-- 
char func1(int x) { ... }
char func2(int x) { ... }
char func3(int x) { ... }

extern char (*functions[])(int);
static unsigned char* __functions() __naked {
__asm
_functions::
	.dw	#_func1
	.dw	#_func2
	.dw	#_func3
__endasm;
}
-- 
  
お分かりでしょうか。C言語からはfunctions外部変数を参照するようにしておいて、実はnaked関数内で定義しています。 なお、スコープをstaticにすることも可能です。その場合は、
_functions:
と、コロンをひとつにします。

上記の場合、メンバとなるfunc1, func2, func3の定義がプロトタイプ宣言と異なっていてもコンパイルエラーは出ません。これは良し悪しで、わざと違う定義にすることもあるかもしれません。たとえば、呼び出し側は常に引数を渡すようにしているが、呼び出された側はその引数を必要としないので、シグネチャに入れない、などということも考えられます。

これを柔軟性と見るか、悪い設計だと見るかは、アプリケーションに依存するでしょうね。

「時間がなくて出来なかった」 - 言ったことありませんか? 自分に都合の悪いことを隠すための便利な言葉でもありますが、私のように抑うつの人には、それだけではないものがあると思います。

実のところ、「時間がない」言い訳をしてしまうのは根の深い問題だと思います。
Reclaim your time - あなたの時間を取り戻す方法
は、そんな人にヒントを教えてくれます。

私自身の経験もあわせて考えると、次のような人は要注意だと思います。
  • スケジュールが埋まると安心、スカスカだと不安
  • 「今日も何もできなかった」と落ち込む
  • ついWebを見てしまう
  • つい夜更かしする
上記のリンク先では20のプラクティスを挙げていますが、私自身の経験で上記とは関係なく有効だと思うのは以下です。

今日やったことをメモする
どんな小さなことでも構いません。「やらなきゃならないこと」ではなく、「やったこと」をメモします。私の場合はTODOリストと合わせて管理していますが、別にTODOを作る必要はありません。ていうか、TODOリストはプレッシャーになってしまうので、人によってはむしろない方がいいかも。
それを一日の終わりに見て、「今日何が出来なかったのか」ではなく、「何をしたのか」を見るようにします。
「時間がない」とお嘆きのあなた、「はぁぁ、今日も何もしなかったなぁ」と鬱の方、騙されたと思ってやってみてください。

ブログを徘徊するネットRPG

| No Comments | No TrackBacks
普段ほとんど全くゲームをしない私ですが、Webサイトを徘徊することが経験値を上げる一環という面白いアプローチがあったので登録してみました。アルカナ・コレクションといいます。

いやほんと、よく考えたと思います。

ヘタレですが、よかったら相手してやってください。

カボチャ頭のときはハロウィンモードです。カモってください。





上記のようなリンク(実際にはFlashをロードするJavaScriptです)をブログに貼らせることによって、参加者(プレイヤー)は訪問者が増えてウハウハ、主催者は参加者とリンクが増えてウハウハ。実によく出来ています。

妹属性の人のための認証モジュール?

| No Comments | No TrackBacks
つい昨日、このブログのCAPTCHA認証がちゃんとセットアップされていないことが明らかになり、直したばかりなのですが。

なんと、「妹認証」なるものがあるそうです。なんでも、妹の質問に答えることで認証されるそうです。動作デモも公開されています。ネタではなく、れっきとしたCAPTCHA認証モジュールです。

技術的には、
  • 日本語のTrueType Fontに対応している
  • 質問と答えをセットで設定できる
というところが興味深いですね。

プログラム本体はPHPです。配布パッケージ内に、画像がpngファイルおよびpsdファイルで格納されている(ただし、psdは私の持っているPhotoshop 6.0では開きませんでした。CS3も持っていますが試してません)ので、妹が嫌な人は画像を変えていろいろできそうですね。

ある意味、パスワードリマインダー的な使い方もできます(質問・答えを動的設定すればよい)し、anonymousかつ会員制サイトみたいなものも作れます。実のところ、意外に隙を突いたアイデアだと思います。

Z80のC言語クロスコンパイル(SDCC)(3)

| 2 Comments | No TrackBacks
リンクについて

sdccでは他のCコンパイラと同様、ライブラリを作って、それをリンクすることができます。ここでは、通常のスタティックリンクと、外部プログラムとリンクするテクニックについて説明します。

ライブラリの作成とリンク

ライブラリはsdcclibというコマンドで作ります。

% sdcc a mylib.lib prog1.o prog2.o

このようにして作ったライブラリをリンクするときに、

% sdcc -mz80 --out-fmt-ihx --no-std-crt0 -o myobj.ihx crt0.o -lmylib.lib

のようにします。ここでcrt0.oは初回に説明したcrt0モジュールです。 sdcclibコマンドはオブジェクトファイル(上記ならprog1.oとprog2.o)の中身をライブラリファイル内にインポートするので、作成後はオブジェクトファイルを消しても大丈夫です。

オブジェクトファイルを消さないなら、sdcclibを使わなくてもライブラリファイルは作れます。そのときのライブラリファイルの中身は、単にファイル名を並べるだけです。
-- mylib.lib prog1.o prog2.o --
このファイルを用意して、同じように

% sdcc -mz80 --out-fmt-ihx --no-std-crt0 -o myobj.ihx crt0.o -lmylib.lib

でリンクできます。mylib.libファイルと同じディレクトリにprog1.oとprog2.oが必要です。

外部プログラムとのリンク

既に作成されたプログラムがメモリ上に存在して、それを利用したい場合は、わざわざ同じルーチンをリンクするのはメモリのムダですね。ここでは、そのような外部プログラムをライブラリとしてリンクするという小技を紹介します。出力にすべてのライブラリコードを入れなくて済むので、コード量が小さくなります。

拙作のSDOSのアプリケーションを作る際は、この方法をとることができるようにしてあります。また、BIOSルーチン(BASICインタプリタなど)などがある場合にも活用できます(ただし、前回説明したsdccの関数呼び出し規約に注意)。

準備

外部プログラムをライブラリとしてリンクできるようにするため、リンク対象となるプログラムはヘッダファイルをきちんと準備しておきます。 ここではSDOSのヘッダ(の一部)を例として挙げます。
-- conio.h void libputc(unsigned char ch); void libputs(char* str); --
もちろん、conio.hの実装も作成します(ここでは省略)。

symファイルからアセンブリファイルの作成

上記の実装プログラムがコンパイル、リンクされると、symファイルというものが生成されます。このファイルは、リンクによって生成されたシンボルがどのアドレスに配置されたかの情報が入っています。
-- sdos.sym (一例) 01:5000 _libputc 01:5010 _libputs --
この情報をもとに、アセンブリファイル(.Sファイル)を作ります。
-- sdos.S .area _SDOSLIB (ABS) .org 0x5000 _libputc:: .org 0x5010 _libputs:: --
アプリケーションの作成 先ほどのconio.hを利用したアプリケーションを作ります。
-- main.c #include "conio.h" void main() { libputc("Hello World"); libputc('\n'); } --
main.cとsdos.S(とcrt0.S)をコンパイル、アセンブルすると、関数libputc、libputsの実装は存在しなくてもリンクすることができます。

ここでのポイントは、sdos.Sでのエリア定義(_SDOSLIB)が絶対アドレス指定(ABS)になっていることです。これにより、main.cをリンクするときに定義されているアドレスが利用されます。 当たり前ですが、リンク対象のプログラムを変更するとアドレスが変わってしまい、アプリケーション側もsdos.Sの再アセンブルと再リンクが必要となるので注意してください。

symファイルからアセンブリファイル作成の自動化

symファイルからアセンブリファイルを作るのはperlで簡単にできます。以下に実例を示します。
-- mklib.pl #!/usr/bin/perl print ".area _CODE2 (ABS)\n"; while (<>) { if (/^\d\d:([0-9A-F]{4})\s(\w+)/) { if ($2 =~ /^s__/ || $2 eq "_main" || $2 eq "gsinit" || $2 eq "_datastart" || $2 eq "_dataend") { } else { print ".org 0x$1\n"; print "$2::\n"; } } } --
上記を使い、

% perl mklib.pl sdos.sym > sdos.S

とすることにより、sdos.Sを生成することができます。なお、このperlスクリプトでは、リンクされるプログラムにあるシンボルのうち、以下を除外しています。
  • _main
  • gsinit
  • _datastart
  • _dataend
  • s__で始まるシンボル(各エリアのスタート位置を示す)
これらは、すべてのプログラムに存在する可能性があるため、sdos.Sに存在すると二重定義になってしまいます。また、アプリケーション作成時にリンクする意味もないでしょう。