2024年2月17日土曜日

80287XL Hacking ソフトウェア編

287XLへの入出力操作は次のようなコードで行えます。特に変わったことはしておらず、データシート通りに信号をH やLにするだけです。

#define FWAIT    while(!(PINC & 0b00000001))
#define WAIT_PRQ while(!(PINC & 0b00000010))
#define ACK_PRQ  PORTA = PORTA & 0b11110111   
#define FREE_PRQ PORTA = PORTA | 0b00001000 

nsigned din() {
  DDRF = 0x00;
  DDRK = 0x00;
  return (PINF << 8) | PINK;
}

void dout(unsigned data) {
  DDRF = 0xff;
  DDRK = 0xff;
  PORTF = data >> 8;
  PORTK = data & 0xff;
}

void wrOp(unsigned opc) {
  unsigned swapped;
  swapped = (opc << 8) | (opc >> 8);
  PORTA = 0b11101000; /* NPS2=H CMD0=L CMD1=L NPS#=L */ 
  dout(swapped);      /* opc */
  PORTA = 0b10101000; /* NPS2=H CMD0=L CMD1=L NPS#=L NPWR#=L */  
  PORTA = 0b01101000; /* NPS2=L CMD0=L CMD1=L NPS#=L NPWR#=H */
}

void wrData(unsigned data) {
  PORTA = 0b11101001; /* NPS2=H CMD0=H CMD1=L NPS#=L */
  dout(data);          /* opc */
  PORTA = 0b10101001; /* NPS2=H CMD0=H CMD1=L NPS#=L NPWR#=L */  
  PORTA = 0b01101001; /* NPS2=L CMD0=H CMD1=L NPS#=L NPWR#=H */
  din(); 
}

unsigned rdData() {
  unsigned data;
  PORTA = 0b11101001; /* NPS2=H CMD0=H CMD1=L NPS#=L */ 
  PORTA = 0b11001001; /* NPS2=H CMD0=H CMD1=L NPS#=L NPRD#=L */  
  data = din();       /* read data */
  PORTA = 0b01101001; /* NPS2=L CMD0=H CMD1=L NPS#=L NPRD#=H */
  return data;
}
   

287のコマンドごとに入出力関数を呼び出すコードを書いてくのはかなり面倒なので、書きやすいように287のコマンドを下のようにマクロで定義しています。 

#define FISUB16(d)  wrOpWr1(0xda20, (d))
#define FSUB(i)     wrOpc(0xd8e0 | ((i) & 0x03)) /* FSUB ST, ST(i) */
#define FSUBd(i)    wrOpc(0xdce8 | ((i) & 0x03)) /* FSUB ST(i), ST */

void wrOpc(unsigned opc) {
  wrOp(opc);
  FWAIT;
}

void wrOpWr1(unsigned opc, unsigned d) {
  wrOp(opc);
  WAIT_PRQ;
  ACK_PRQ;
  wrData(d);
  FREE_PRQ;
  FWAIT;
}

試しに、287XLを使ってマンデルブロ集合のASCIIARTを動かすコードを書いてみたところ、 実行時間は2.6秒でした。



287XLを使わずソフトウェアで実行すると、Arduino Megaは結構速く、2.1秒程度で実行できてしまいます。Arduinoに接続した場合、I/Oポートを介して操作するオーバーヘッドが大きかったり、1コマンドを実行するためにステップ数が多くなるので、あまり速くできないようです。 

GitHubにコードを公開しておきます。https://github.com/4sun5bu/287Hack

80287XL Hacking ハードウェア編

 最近のCPUは内蔵しているのでなじみがないですが、数値演算プロセッサ(FPU)は、昔は外付けで、なかなかなお値段の憧れの石でした。Z8000もFPUを内蔵しておらず、ZilogはZ8070のリリースをアナウンスしていましたが、結局出なかったようです。CP/M-8000には、Z8070をエミュレートするライブラリが含まれているのですが、試してみたところ実用的なスピードではありません。

昔、Intelの80286用の数値演算プロセッサ80287がI/Oデバイスのように扱えると、どこかで読んだ気がしていたので、Z8001につなごうと80287XLを入手してありました。本当につなげらるのか事前確認するため、これをArduino Megaに接続して検証してみました。

接続は下図になります。


クロックには10MHzを供給し、CKMをLにして内部で2分周しています。残りの信号やデータバスはArduinoのI/Oに接続します。基本的に、データシートの通りに信号を動かしながら、データバスからデータを与えたり読んだりすれば動作します。

ちゃんと調べずに高性能というだけで287XLを入手していたのですが、ただの287だと接続信号数が多くむずかしいようです。287XLではNCになっていますが、287だとこれらのピンに役割があり、CPUの対応する信号に接続することになっていてます。287はCPUの動作状態を監視しているようですが、287XLではこれらは要らないと判断され廃止されたようです。

データシートを読んでみると、確かに287/287XLは286からはI/Oデバイスと扱われており、アドレスが割り当てられています。NPS#1とNPS2がチップセレクトにあたり、CMD0とCMD1がアドレスに当たり、下のような組み合わせがあります。

 CMD0=L, CMD1=L :コマンド書き込み、CWとSWの読み込み
 CMD0=H, CMD1=L:データ読み書き
 CMD0=L, CMD1=H:例外ポインタ書き込み

例外ポインタ書き込みは、まだ何なのかわかっていません。これ以外の組み合わせは予約されています。NPRD#とNPWR#は、I/Oリードとライト信号となっています。

データバスは16bitなのですが、x86系はリトルエンディアンなので、コマンドやデータをやり取りするには少し注意が必要です。データの場合は、D15-D8が上位バイトでD7-D0が下位バイトで問題ありませんが、コマンドは上下バイトを入れ替える必要があります。

x86のアセンブリ言語をみると、287のコマンドに続いてアドレッシングモードを含んだオペランドが来るのですが、287からはメモリアクセスができないので、必要な場合はCPUがメモリアクセスして287にデータを渡しています。その際、287はPEREQをHにしてデータ転送を求めていること示し、CPUはデータ転送をする前にPEACK#をLにし、データ転送要求を受け付けたことを287に伝えます。データは複数回数行う場合もあり、1転送ごとにPEACK#をHに戻す必要があるのか、すべての転送が終わるまでLに保持しておいても良いのかは不明です。試してみた感じでは、PEACK#は一度もLにせずHのままにしておいても動作するようです。

BUSY#は287がコマンドを実行中かを示しているので、この信号をみて処理の完了を確認でき、ERROR#でコマンドの実行が正常に終わったかを判断できます。

これだけ知っていれば、287XLは動かせます。