【PLCC】信号線12本のPCIデバイス作ってみた【ABEL】
2014/05/18 作成

信号線12本のPCIデバイス作ってみた


PCIの拡張ボードをいつか作ってみたいとずっと思っていましたが、今やPCIもすっかりレガシーの仲間入りです。何か手軽な所から作り始める事は出来ないかと考えていた結果、コンフィギュレーション空間でのアクセスに限定し、更に正規のプラグアンドプレイでの動作を放棄する事で接続する信号線を最小限に出来る事に気づきました。
しかしそんなものが実際に動作するでしょうか? そう思ったとき俄然好奇心が涌いてきたので実際に試してみました。
CPLD に、Xilinx XC9536-15PC44C を使用。PLCC大好き。
LED は、HP 5082-7340。デコーダ内蔵。
(CPLDの方でドット・マトリクスを駆動しているのではない。手抜き。)
信号線12本 と 電源 , RST(リセット) , CLK(クロック)
パスコン(ノイズ対策のバイパスコンデンサ)一つ無いというのは暴挙なので真似しないように。
実際に動作したHDLのソース。
LED_8BIT.ABL 例によって HDL は ABEL 。ABEL大好き。
MODULE LED_8BIT

CLK PIN 7; " Should be mapped to GCK

IDSEL PIN 18;
FRAME PIN 20;
IRDY PIN 22;
TRDY PIN 24;
DEVSEL PIN 26;
STOP PIN 28;

RST PIN 39; " Should be mapped to GSR.

CBE3..CBE0 PIN 19,25,27,34;

AD3,AD2 PIN 29,33;

LED_A3..LED_A0 PIN 2,3,4,5;
LED_B3..LED_B0 PIN 38,43,44,1;

DEBUG PIN 6;


CBE = [CBE3..CBE0];

LED_A = [LED_A3..LED_A0];
LED_B = [LED_B3..LED_B0];


FRAME_REG node istype 'reg';

CBE_REG3..CBE_REG0 node istype 'reg';
CBE_REG = [CBE_REG3..CBE_REG0];

AD_REG3..AD_REG2 node istype 'reg';
AD_REG = [AD_REG3..AD_REG2];


FRAME_START node;

ST0,ST1,ST2 node istype 'reg';

WR_READY node;

" Initial value is 0x39.
XILINX PROPERTY 'INIT=S LED_REG5';
XILINX PROPERTY 'INIT=S LED_REG4';
XILINX PROPERTY 'INIT=S LED_REG3';
XILINX PROPERTY 'INIT=S LED_REG0';
LED_REG7..LED_REG0 node istype 'reg';
LED_REG = [LED_REG7..LED_REG0];

RD_ACTIVE3..RD_ACTIVE0 node istype 'reg';

XILINX PROPERTY 'BUFG=OE AD_OE';
AD_OE node;

XILINX PROPERTY 'BUFG=OE TGT_OE';
TGT_OE node;

CMD_CSREAD = ^b1010;
CMD_CSWRITE = ^b1011;

EQUATIONS

FRAME_REG := FRAME;
FRAME_REG.clk = CLK;

FRAME_START = FRAME_REG & !FRAME & IDSEL & ((CBE == CMD_CSREAD) # (CBE == CMD_CSWRITE));

CBE_REG := CBE;
CBE_REG.clk = CLK;
CBE_REG.ce = FRAME_START;

AD_REG := [AD3..AD2];
AD_REG.clk = CLK;
AD_REG.ce = FRAME_START;

ST0 := FRAME_START & (CBE == CMD_CSREAD) & ([RD_ACTIVE3..RD_ACTIVE0] == ^b1111);
ST1 := ST0 # (FRAME_START & (CBE == CMD_CSWRITE));
ST2 := ST1;
[ST2..ST1].ce = !ST1 # !IRDY;
[ST2..ST0].clr = FRAME & IRDY;
[ST2..ST0].clk = CLK;

TGT_OE = ST1 # ST2;

DEVSEL = !ST1;
DEVSEL.oe = TGT_OE;

TRDY = !ST1;
TRDY.oe = TGT_OE;

STOP = !ST1;
STOP.oe = TGT_OE;


[LED_A,LED_B] = LED_REG;

WR_READY = ST1 & !IRDY & (CBE_REG == CMD_CSWRITE);

LED_REG := [AD3..AD2,AD3..AD2,AD3..AD2,AD3..AD2];
LED_REG[7..6].ce = WR_READY & (AD_REG == 3);
LED_REG[5..4].ce = WR_READY & (AD_REG == 2);
LED_REG[3..2].ce = WR_READY & (AD_REG == 1);
LED_REG[1..0].ce = WR_READY & (AD_REG == 0);
LED_REG.clk = CLK;


[RD_ACTIVE3..RD_ACTIVE0] := ^b1111;
RD_ACTIVE0.ce = WR_READY & (AD_REG == 0);
RD_ACTIVE1.ce = WR_READY & (AD_REG == 1);
RD_ACTIVE2.ce = WR_READY & (AD_REG == 2);
RD_ACTIVE3.ce = WR_READY & (AD_REG == 3);
[RD_ACTIVE3..RD_ACTIVE0].clk = CLK;
[RD_ACTIVE3..RD_ACTIVE0].aclr = !RST;

[AD3..AD2] =
	(AD_REG == 0) & LED_REG[1..0] #
	(AD_REG == 1) & LED_REG[3..2] #
	(AD_REG == 2) & LED_REG[5..4] #
	(AD_REG == 3) & LED_REG[7..6];
[AD3..AD2].oe = AD_OE;
AD_OE = ST1 & (CBE_REG == CMD_CSREAD) & ([RD_ACTIVE3..RD_ACTIVE0] == ^b1111);


DEBUG = FRAME_START;


END

解説

AD線

PCIバスのデータ転送の中核となるのはAD0〜AD31の32本の信号線です。PCIバスではアドレスの伝送とデータの伝送を同じ信号線を利用し時分割で行っています。(補足すると32bitのPCIバスであってもオプションとしてアドレスを2回に分けて送ることで64bitのアドレス空間に対応出来る仕様になっています。)
今回制作したデバイスでは、AD線のうちAD2とAD3の2本だけを接続しています。これによって配線の数と半田付け作業の手間を大幅に削減しています。AD0とAD1を使わないのは、コンフィギュレーション空間のレジスタアドレスが4の倍数になっている事を利用して下位2ビットを別の用途(コンフィギュレーション・サイクルのタイプ指定)に利用するようになっているためです。これ(タイプ)を意識する必要があるのはバス・ブリッジだけです。

前述のようにPCIバスではアドレスの伝送とデータの伝送で信号線を共用していますが、簡単に言えば32bitのアドレスを送って次に32bitのデータを送るという具合で動いています。(アドレスの後にデータを連続で繰り返すバースト・モードも存在しますが、コンフィギュレーション空間へのアクセスでは利用されません。) 2本のAD線だけを接続しているのでバスを流れている32bitのうち2bitだけが観測可能です。したがって今回制作したデバイスにとってはアドレスが2bitでデータも2bitという事になり、2bitのアドレスで指定出来る範囲は0〜3の4通りですから、結局8bit分のレジスタ空間が定義される事になります。

コンフィギュレーション空間

PCIバスは、バス上のデバイスを自動的に認識しメモリアドレスとI/Oアドレスを競合の無いように自動的に割り当てる仕組みになっていますが、これを実現しているのがコンフィギュレーション空間に定義されたコンフィギュレーション・レジスタです。

システムの電源が入ってリセット信号がディアサートされた直後、PCIデバイスはアドレスの割り当てられていない状態にあります。アドレスが決まっていないのでは個々のデバイスにアクセスする方法がなにもないという事になってしまいますが、それでは困るのでいつでもデバイスを個別に指定してアクセスする仕組みとしてコンフィギュレーション空間がある訳です。その代わりコンフィギュレーション空間ではデバイス毎に割り当てられるアドレス空間が小さくなっています。

今回制作したデバイスではコンフィギュレーション空間でのアクセスのみを受け付けますが、まっとうな形でのコンフィギュレーション・レジスタは実装していません。(信号線を省略しているため出来ません。)

IDSEL

コンフィギュレーション空間にアクセスする場合、C/BE[3:0]# にアクセスの種類を示すバス・コマンド、コンフィギュレーション・リード(^b1010) 又は コンフィギュレーション・ライト(^b1011) が出力されますが、どのデバイスへのアクセスかの識別には通常のバス・サイクルでは使用しないIDSELという信号線を使います。各デバイスは自分に繋がれているIDSEL信号がハイ(High)の時、アクセスの対象が自分であると判断します。(バス・ブリッジはタイプ1のコンフィギュレーション・サイクルを認識してそこに指定されている番号のデバイスのIDSELをアサートするという役割を果たしています。)

IDSELのためにマザーボード上のスロットの数だけ別途配線をするというのは見合わないコストなので、実際にはAD11〜AD31のどれかを100Ωの抵抗を介してIDSELに接続するというトリックが一般的に使われています。抵抗を入れるのは余分な接続をした事によるバスへの負荷を抑えるためです。抵抗が入っているためにIDSELは電圧の変移が遅くなるので、ホスト・ブリッジやバス・ブリッジはその分アイドル・フェーズを挿入するなどして時間をかけてドライブするような工夫をしているはずです。IDSELが有効なのはコンフィギュレーション・サイクルのアドレス・フェーズだけという事になります。

コンフィギュレーション・サイクルでは C/BE[3:0]# と IDSEL を見て自分がターゲットである事を判断出来るため、今回制作したデバイスでは本来アドレスのデコードに不可欠のはずのAD線を省略しても動作できるという訳です。

プラグ・アンド・プレイ避け

前述のように今回制作したデバイスはコンフィギュレーション空間でのアクセスを受付ますが、コンフィギュレーション・レジスタの形では実装していないので、BIOSやOSのプラグ・アンド・プレイ機能によってでたらめに認識されてしまう可能性があります。(ほぼ確実に問題となります。) これを避けるために、4つのアドレスにそれぞれ書き込みがあった後で初めて読み込みに反応するように工夫しました。プラグ・アンド・プレイではまずベンダID、デバイスID、クラスコードの読み込みがあってその後書き込みの必要な手順に移るはずなので、上記のように工夫する事でバス上に存在しないものとして無事無視してもらうことが出来ました。

テスト環境とテストプログラム

Linux

テスト環境のOSとして、Knoppix 7.0.4 を USBメモリにインストールしたものを使用しました。

バス・アドレス

テストプログラムを作成実行する前に、PCIデバイスを接続したスロットのバス・アドレスを調べる必要があります。
制作したデバイスはOSに認識されない仕様であるため、何か別のPCIカードを用意し、テストに使用するPCIスロットに接続してOSを起動します。
例えば PowerVR の載ったPCIカードを挿して起動し、
$ lspci
として
04:09.0 Multimedia controller: NEC Corporation PowerVR 3D (rev 01)
の様に対応する行を探します。この場合バス番号が 4、デバイス番号が 9 です。

デバイスの接続

通常は電源を切った状態で接続するのが作法ですが、制作中の段階では接続しているとBIOSに検出されてエラーになり、OSが起動出来ないという事もありました。しかし接続しなければ動作の検証が出来ないので困ります。この様な場合OSが起動してから電源が入っている状態でPCIスロットにデバイスを接続するというのも一つの手です。もちろんリスクは存在しますが、意外と壊れないものだと分かるので過剰に恐れる必要がない事を体感で理解できます。何事も慣れです。

I/Oポート0xCF8,0xCFCによるコンフィギュレーション空間のアクセス

PC環境では、I/Oポートの0xCF8と0xCFCを使ってPCIデバイスのコンフィギュレーション空間にアクセス出来ます。 以下のサンプルは、制作したデバイス内の8ビットのレジスタへの書き込みと読み出しを行います。
バス番号とデバイス番号は、プログラム中にハードコードしています。
実行には root 権限が必要です。
write-cfgreg-with-ioport.c
read-cfgreg-with-ioport.c

メモリ・マップド・コンフィギュレーション空間を使用したアクセス

PCI-Expressのシステムではメモリ・マップド・コンフィギュレーション空間という、その名の通りPCIデバイスのコンフィギュレーション空間を直接ホストCPUのアドレス空間にマップする仕組みが導入されています。I/Oポートによるアクセスと違ってCPUから直接アクセス出来、競合の可能性のある共有リソースを介さずに済むのが利点です。実はコンベンショナルなPCIデバイスでもこの仕組みの恩恵に与る事が出来ます。
$ cat /proc/iomem
として
e0000000-efffffff : PCI MMCONFIG 0000 [bus 00-ff]
という様な行が含まれていれば使用可能です。

以下のサンプルではプログラム中に上記の様に調べた値をハードコードしています。
やはり実行には root 権限が必要です。
counter-with-mmcfg.c

その他(後で整理する)

まとめ

おまけ

リンク

footer separator
へにゃ
Applauseこと 寺川 愛印(Ein Terakawa)
E-mail: applause@elfmimi.jp
https:/applause.elfmimi.jp/
害虫駆除にご協力を。