PCIは、現在のマイコンシステム用拡張バスで事実上の標準規格となっていて、現在発表されているパソコンは全てPCIバスを採用するようになっています。これだけ身近になっているにもかかわらず、このバスを活用した製作モノにはほとんどお目にかかることがありません。PCIはその高機能さゆえ複雑難解でアマチュアの手に負える物ではないと思われているようです。
実際、PCIの規格を満たす機能をフル実装したボードをTTLの個別部品で自作ることは不可能でしょう。それじゃどうにもならないじゃないかと言うところですが、PCIはシステム全体としては複雑でも、基本的なところはきわめて簡単な手順で動作しています。このため、PCIのターゲットデバイスとしてとりあえず動作するという程度のものなら、PLDを利用して自作することも可能なのです。ただし、このようにして自作したボードは、完全な PCIデバイスではありませんので、どのようなシステムでも動作するとは限りません。飽くまで PCIの動作を理解するための実験ということでいきましょう。今回はとりあえず動かしてみるということが目的ですので、製作するのは単純な PIOとします。/p>
PCIは完全な同期バスですので、全ての動作の単位はクロックを基準にしたものとなります。製作する拡張ボード(ターゲットデバイス)にはイニシエータからのコマンドを解釈して順次実行するステートマシンの概念が必要になってきます。この I/F回路では、PLDでステートカウンタとシーケンスロジックを実現するようにしています。次にステートカウンタの値と動作の対応を図にして説明します。
バスがアイドル状態。この状態から新たなバスサイクルの始まりを検出します。このデバイスに対してI/Oサイクルが開始された場合は、STATE<2>またはSTATE<5>に移ります。それ以外のバスサイクルが始まった場合は、STATE<0>に移行します。
バスが非アイドル状態。バスがアイドル状態になるまで待ちます。この状態からは新たなサイクルには移行しません。
ライトサイクル実行中。DEVSEL#をアサートしてライトサイクルに応答します。同時にTRDY#をアサートして転送の準備ができたことをイニシエータに知らせます。この状態でIRDY#がアサート(イニシエータが転送準備OK)で、なおかつFRAME#がディアサート(最終サイクル)の場合は、STATE<3>に移行します。
ライトサイクル終了。DEVSEL#とTRDY#をディアサートにドライブします。通常は次のクロックでDEVSEL#とTRDY#を開放してアイドル状態に移行します。が、この状態でこのデバイスに対してリードライトコマンドが発行された場合には、アイドル状態を経由せずに直接STATE<2>またはSTATE<5>に移行します(高速バック・トゥ・バック・トランザクション)。
リードサイクル開始。AD[31:0]をドライブするエージェントの切り替えが発生するので、衝突を避けるために1クロックの緩衝時間を取ります。次のクロックでSTATE<6>に移行してリードサイクルに応答します。
リードサイクル実行中。DEVSEL#をアサートしてリードサイクルに応答します。同時にTRDY#をアサートしてデータの準備ができたことをイニシエータに知らせます。この状態でIRDY#がアサート(イニシエータが転送準備OK)で、なおかつFRAME#がディアサート(最終サイクル)の場合は、STATE<7>に移行します。
リードサイクル終了。DEVSEL#とTRDY#をディアサートにドライブします。次のクロックでDEVSEL#とTRDY#を開放してアイドル状態に移行します。
このステート番号は定義していません。誤動作によりこの状態に入ってしまっても次のクロックで STATE<0>に移行して正常ループに復帰します。
最も単純なバスサイクルです。ターゲットデバイスは、2クロック目でFRAME#がディアサートからアサートに変わり、アドレスフェーズであることを検出します。同時に、コマンドとアドレスを取り込み、自分に対してのアクセスの場合は、DEVSEL#をアサートして応答します。この場合は1クロックで応答しているので高速デコードです。
次のクロックでIRDY#がアサートされている(書き込みデータの準備ができている)ことを確認してデータを取り込みます。同時にFRAME#がデイアサートされているので、そのデータが最後の転送であることを知ります。
最後のデータを取り込んだらDEVSEL#とTRDY#をディアサートにドライブして、その次のクロックでDEVSEL#とTRDY#を開放してサイクルを終了します。
最も単純なリードサイクルです。読み込みでは、サイクルの途中でAD[31:0]をドライブするエージェントが切り替わるので、衝突防止の緩衝時間として1クロック余計に時間がかかります。
ターゲットデバイスは、2クロック目でFRAME#がディアサートからアサートに変わり、アドレスフェーズであることを検出します。同時にコマンドとアドレスを取り込み、自分に対してのアクセスの場合は、DEVSEL#をアサートして応答します。この場合は2クロックで応答しているので中速デコードです。高速デコードでもかまいませんが、AD[31:0]にデータを乗せてTRDY#をアサートするのは1クロック以上後でなければなりません。
AD[31:0]にデータを乗せてTRDY#をアサートした次のクロックで IRDY#がアサートされている(データがイニシエータに読み込まれる)ことを確認してそのデータ転送を終了します。同時にFRAME#がデイアサートされているので、そのデータが最後の転送であることを知ります。
最後のデータを転送しおわったらDEVSEL#とTRDY#をディアサートにドライブして、その次のクロックでDEVSEL#とTRDY#を開放してサイクルを終了します。
バースト転送は通常のシングル転送と区別されているわけではありません。ターゲットは、データを1回転送する毎にFRAME#をチェックしていて、FRAME#がアサートされ続けている場合はデータの転送が続くことを知り、それによりバースト転送を実現しています。バースト転送は主にメモリデバイスに対して行われますが、I/Oデバイスへのバースト転送も可能です。
PCIバスがバースト転送で最も性能が出るように設計されていることは、アドレスとデータが時分割であることからも容易に想像できます。なぜなら、バースト転送においてアドレスは最初に1回指定するだけで十分だからです。これにより信号線数を大幅に抑えて動作の安定とコストの低減を図っているのです。
バスを効率よく使用するために、高速バック・トゥ・バック・トランザクションという方法が定義されています。
通常、バスサイクル(トランザクション)の間には最低1クロック期間のアイドルサイクルが存在します。これは出力同士の衝突が発生するのを防ぐための緩衝期間なのですが、バスサイクルの間で信号線をドライブするデバイスの切り替えが発生しないという条件が成立する場合に限り、このアイドルサイクルを省略できます。同一ターゲットデバイスへのアクセスでは常に使用可能です。
さて、この製作ではPCIデバイスとして要求される機能を一部省くことにより回路を単純化して再現性を確保しています。省いた機能とそれによる弊害について説明しておきましょう。まともな PCIデバイスを設計している方が見ると卒倒しそうな内容で、もはやPCIターゲットとは言えないですが、パソコン上で実験してみる程度なら特に問題になることはないです。
PCIではコンフィグレーションレジスタを実装することによりデバイスの存在がシステムに認識され、PCIデバイスとしての機能を設定したり、リソースの競合が発生しないようにアドレスを移動したりできるようになっています。
このボードではコンフィグレーションレジスタを実装せずにアドレスを固定的にデコードしています。デバイスの存在がシステムに認識されないので、リソースの競合が発生しないように I/Oアドレスを設定してやる必要があります。DIP SWのあるPCIボードなんて笑えますね(笑)。
パリティ線のチェック及び駆動をしませんので、リード動作では必ずパリティエラーが発生します。イニシエータ(この場合はホスト-PCIブリッジ)のコンフィグレーションレジスタの4バイト目のbit6(パリティエラー応答ビット)が立っている場合はパリティエラーを検出するとそれに応じた動作(NMIなど)をします。しかし、パソコンではこのビットが通常は0になっていますので、パリティ機能を実装しなくても動作に影響はありません。
PCIではI/Oデバイスも32bitフルデコードが義務付けられていますが、このボードでは下位16bitしかデコードしていません。しかし、パソコンで使うぶんには64Kバイト以上のI/O空間に対するアクセスが発生することはないので、特に問題にはなりません。
規格では安定動作のため1本の信号線に接続できる入力は 1個までという決まりになっています。このボードでは AD[31:16]に2個接続していますので規格外です。とりあえずマージンはあるようで、今回は既に使用中の3枚にこのボードを加えた4枚挿しで実験しましたが正常に動作していました。
まず、部品を揃えることになります。PCI用のユニバーサルボードにはサンハヤトの MCC-331 (\5,000程)を使用しました。このボードにはマウント金具が付いているので、なかなか使いやすいです。
PLDは 7ns以下の物を使用してください。実はバスタイミングの規格からするとこれでも間に合わない位なのですが、これ以上速いのは入手困難ですのでマージン頼りで使用しました(^_^;(普通は問題無いですけどね)。
それ以外の部品はごくありふれた物ですので、メーカー等まで選定する必要はないでしょう。入出力は、それぞれ 32bit欲しいところでしたが、今回はコネクタの実装スペースの問題で、それぞれ 16bitとしました。
単なるディジタル回路ですので、組み立てもそれほど気を使うことはないと思います。配線の本数が多いので UEWで配線するとよいでしょう。ラッピングワイヤだと山盛りになってしまいます。いちばん注意しなければならないのは、配線を間違えないことでしょうか。エッジコネクタの切り欠きの部分で端子番号が不連続になっていますので、ここで間違えやすいです。実際わたしも何度か間違えました(笑)。特に電源端子の間違いはM/Bを壊す恐れがありますので十分に注意してください。また、最大33MHzとかなり高速動作しますので電源ラインは十分に強化しておくべきです。
最初にDIP SWでこのボードのI/Oアドレスを指定します。下位2bitはデコードしていないので、連続した4bytesを占有することになります。このとき他のPCIデバイスとI/Oアドレスが競合するとバスが衝突しますので、誰も使わないI/Oアドレスを選ばなければならないのは言うまでもありません。
X300h~X30FhがISA試作ボード用に開放されていますので、この辺を乗っ取るといいと思います。例えば、DIP SWでF300hを設定すると、F300h~F303hを占有します。つまり、このアドレスへのI/Oアクセスには、このボードが応答するというわけです。
PCI-ISAブリッジは、PCIバス上のデバイスが誰も応答しない場合にのみ応答(消去法的デコード)してISAにバスサイクルが発生します。このため、ISAバス上のデバイスと同じアドレスを設定してもこのボードが先に応答してPCI-ISAブリッジは動作しないので、バスの衝突は起きません。
さて、無事動作することが確認できたらパフォーマンスチェックといきます。MPUの I/Oストリング操作命令で連続アクセスして単位時間当たりのサイクル数を計測します。その結果は....

当然ですが、ISAに比べてPCIは圧倒的なスピードを示しました。クロック数で計算してみると、ライト動作で6clks/cycle、リード動作で9clks/cycleということになります。しかし、理論値ではそれぞれ 3clks、4clksですので、それほど転送レートは上がっていないとも言えます(バスアイドル状態が長い)。これは恐らくHOST-PCIブリッジでのオーバーヘッドが原因かと思われます。もっとも、PCIはバースト転送で高いパフォーマンスが出るように設計されていますので、ランダムアクセスで分が悪いのは仕方無いです。今回は PCIチップセットとしてTriton(82437TX)を搭載した M/Bを使用しましたが、他のチップセットを載せた M/Bではまた違った値が出てくるかもしれませんね。
また、ISAにおいて16bitアクセスと 8bitアクセスでは倍近いスピードの違いがあります。これは I/Oデバイスが 16ビットデバイスとして応答すると I/Oサイクルも短縮されるからです。ISAバスでちょっとスピードを稼ぎたいときは、8bitデバイスでも-IOCS16を操作するようにすると良いでしょう。trの値は BIOSで設定したI/Oリカバリータイムです。
このレポートはいかがでしたでしょうか。複雑そうに思えて取り付き難かったPCIも、このようにアマチュアレベルでも一応いじってみることができました。全体的には複雑そうでも、案外単純な取り決めを基本として動作していることが分かるかと思います。
これを機会にPCIについてもっと知りたくなったのなら、PCIの規格書と次の文献を読んでみるといいと思います。このレポートでもPCIについての情報をこの文献から得ました。また、個人レベルでPCIボードを開発しようというなら、各PLDメーカから出ているPCIボード開発キットを利用するのが一番の近道です。
参考文献:OPEN DESIGN No.7 PCIバスの詳細と応用へのステップ, CQ出版
