CPU実験室

誰も見向きもしない古いCPUをいじって動かしてみようというプロジェクトです

ICU用アセンブラ

ICUニーモニックを受け付けるアセンブラを作ってみました。
 
マシンコード自体は16ビット固定長で上位4ビットは16種類のインストラクション、下位12ビットはそのまま操作対象の実効アドレスなので非常に単純な構造です。
初めはMASMのマクロ定義で展開しようとしたのですが、データアドレスの参照は容易なのですがJMP命令で飛び先アドレスの解決がうまくいかず行き詰ってしまいました。
 
そこで思いついたのがEXCELのワークシートを使う方法です。
オペランドとオペコードをそれぞれセルに書き込むと、あらかじめ定義しておいた表からvlookup関数で対応するインストラクション、アドレス値を引き当て、上位下位合成することでマシンコードを得ます。
JMPの飛び先は合致するラベルを検索して行番号からわかります。
 
こうすることで1行ごと入力するたびにリアルタイムでアセンブルされ、検索に失敗すればエラーコードも表示するツールになりました
 
イメージ 1
 
 
 

モニタ組込み

ICUボードにモニタプログラムを搭載しました。
 
といっても普通、ワンボードコンピュータに載っているようなCPU自身が制御するレジデント型のモニタではなくシステム制御のPIC18F4550がICUのバスに介入し、ICUと無関係にデータやステータスを覗いたりメモリを書き換えたりする、ICEに近い動作をします。
 
とりあえず
・プログラムの実行/停止
・コードメモリダンプ、データメモリダンプ
ICUへのクロック周波数の設定
 
といったコマンドを実装しました。
ホストのターミナルプログラムからシリアルを通してコマンドを送ることができ
いかにも直接ICUが応答しているかのように見えます
 
 
イメージ 1
 

データメモリ実装

最後に残していたデータメモリのHM6287を実装してハードウェアは完成しました。
その上側にあるのが標準ロジックICに紛れて目立っていませんがこのボードの御本尊、MC14500です。
 
 
イメージ 1
 
このメモリの動作確認を兼ねて、もう少し大きいプログラムを動かしてみました。
 
8ビットCPUでもプログラム次第で多倍長演算や浮動小数点演算ができるように
1ビットCPUであるMC14500でも複数ビット合わせれば「数」を扱うことができるはずです
ここでは8ビットの変数をインクリメントすることを考えます。
MC14500には当然加算器はないので、1ビットずつロジック演算していかなければなりません。たとえば4ビットの数値に+1するためには以下のように半加算器を並べ、桁上げ信号(キャリー)を伝達するロジックが必要です。
 
イメージ 3
 
これをICUのソフトウェアで8ビット分を実現するとこうなります
被演算バッファをデータメモリの800h~807h、キャリービットを保持するバッファを810h~817hに割り当てています。
 
イメージ 2
 
最後に被演算バッファの8個のデータをLEDアレイのアドレスにコピーしてやると
バイナリ的にLEDがチカチカするのがわかります。
 
多ビットのCPUであれば、たとえば「INC A」で済むところがICUにとってはえらく面倒なことになってしまいます

インバータ挿入

 
デコーダを正論理出力の74HC239に貼り替えようかと思いましたが、入力ポートの方の論理は正しいので2本のデコード出力のみにインバータを挿入することにしました。
空きゲート、予備パタンは無いので基板の裏にSOPのシングルゲート(TC7W04F)を接着剤で貼り付けUEWで手配線です
 
かなりみっともない状況になっています
 
 
イメージ 1
 
 
これでなんとか論理的にはOK。
出力ポートの先につけたLEDを点滅させるプログラムを実行させてみます。
 
 
イメージ 3
 
 
プログラムを書いてみると気付くのですが、ICUはプログラムバスとデータバスが完全に分離されたハーバードアーキテクチャになっています。
そのため、プログラムコードにデータの即値を埋め込むことができません。
つまり、たとえばレジスタに直接「1」をセットすることすらできないということです。
 
ここでは入力ポートがプルアップされていることを利用し、入力ポートのビット0(アドレス0x010)を「恒真」ビットとし、ここを参照することによって論理「1」を取得するようにしています。
 
 
イメージ 2
ようやくLEDアレイのチカチカ(ナイトライダー風)テストができました。
プログラムのコーディング、DLL自体はPICに対して行うのですが、LEDをチカチカさせているのはあくまでも「MC14500」です
 
・・動作クロックはPICのCCPユニットから貰っています。。
ただし3個並んだ一番左のSWをOFFにするとICU内蔵の発振器で自走します

I/Oポート接続

 
ICU自体の動作は確認できたのでデータバスに接続されたI/Oの動作確認をしていきます。バス接続についてもいまさらながら構成図を書くとこんな具合になります、
 
イメージ 3
 
 
 
16ビットのデジタル入出力と2kビットのデータメモリが接続されますがデータバスとなっているところはあくまでも「1ビット幅」です。
ですから16ビット入出力は一見パラレルのようですが実は1ビットづつ個別のI/Oアドレスを占有します
 
 
イメージ 1
最後に残ったICもソケットに実装し、さて動作テストというところでまたまた設計ミスが発覚がーん。
 
MC14599のチップイネーブルを負論理と思い込んでアドレスデコーダ74HC139の負論理出力をそのまま接続していました。
なんか今回の設計ボロボロです。
 
 
イメージ 2
 
 
 
 

ハード修正断念

JMP信号の微分回路はシミュレーションからざっくりC=220pF、R=1kΩとし
だいたい150~200nS幅の正パルスを得られるようにしました
 
イメージ 3
 
 
 
この回路をブレッドボード上で実現してみたのですが、結果が思わしくなくジャンプ先のアドレスが正しくありません。パルス幅が狭すぎるためか、プリセットが行われずカウンタがインクリメントしてしまったりかといって少し延ばしてやると再び発振状態になってしまいます。
 
 
イメージ 1
 
 
このプログラムカウンタはそもそもプリセットもクロック同期する74HC161を使うべきでした。
といっても後の祭り・・・
 
ここではハードでの対応はあきらめてソフトでなんとかしてみます。
要はJMPコードが発行されたとき、その飛び先にあるコードのオペランドが自身の絶対アドレスを指していればとりあえずフェッチミスは防げるはずです。
コードを書くとこのような具合:
 
イメージ 2
 
飛び先(LOOPラベル)にNOPコードを置き、そのオペランドにそのコードが置かれるアドレスをセット。
アセンブラ風の表記でいえばロケーションカウンタをつかって「NOP $」となります。
NOPに実効アドレスが付いているのも変ですが。

メモリ発振

JMPコードが実行される仕組みを考えて見ます
ICUは4ビットのJMPコード($C)を認識すると単にJMP出力をHにするだけです。
一方、プログラムカウンタはメモリから出力されている残り12ビットをJMP出力によってカウンタにプリセットします。
そこで、プログラムカウンタに使用したMC14516の真理表をじっと見てみると
どうもプリセット機能があやしい・・・クロックとは関係なく非同期に行われるようです
イメージ 2
 
すると、ICUのJMP出力=カウンタのプリセット入力がHの期間はカウンタのラッチがトランスペアレントになりプログラムメモリで以下のような閉ループができてしまうことになります
このときアドレス入力がクロック同期されていればいわゆる普通の順序回路ですが、
ダイレクトにフィードバックされると何が起こるかわかりません
イメージ 1
今メモリの内容が{$1001,$2000,$C000}であったとき
ある瞬間、メモリから$C000のデータが出力されていると下位12ビットの$000がメモリのアドレスに入力されます。
メモリは即時にアドレス$000に存在するデータ$1001を出力し$001がメモリのアドレスに入力されメモリは$2000を出力する・・・・
といったリスト構造の無限参照をおこなってしまいます。
これはクロックとは関係ない非同期動作でデバイスの遅延時間で決まる最高速度で実行され、リングオシレータのように発振状態となります。
 
まさに今回そのような状態になっているといえます。
メモリの遅延時間は25ns、カウンタ入力~出力の遅延が315~630nsとされているので
発振周波数1.6MHzはありえます。
 
ch1.:clock  ch2.:アドレスA0  ch3.:JMP出力
 
イメージ 3
 
 
 
これを回避するには次のメモリのデータが読み出される前にラッチを閉じてしまえばよいわけでJMP出力をCR微分して幅を100~200nsに狭めてからプリセット入力すれば良さそうです