なでしこさんで、『CPUの創りかた』で題材となっている4ビットCPU「TD4」のエミュレータを作ってみました。
TD4
TD4は、4ビットのプログラムカウンタを持ち、8ビットの命令を16個格納できるROMにプログラムを格納して実行します。
また、A・Bの2個の4ビットのレジスタと、1ビットのフラグレジスタ1個、各4ビットの入力ポートと出力ポートを持ちます。
以下のサイトに、回路図や命令セットが載っています。(公式ではありません)
CPUの創りかた TD4
TD4の1ステップ
回路図より、TD4は、クロックが1回入力されると、以下のような動作をするということが読み取れます。
(以下、命令の最下位ビットを0ビット目、最上位ビットを7ビット目とします)
加算対象データの選択
命令に基づき、
- Aレジスタ
- Bレジスタ
- 入力ポートの値
- ゼロ
の中から、命令のデータに基づいて後に加算するデータを選択します。
命令の4ビット目と7ビット目のORをx、5ビット目をyとおくと、xおよびyの値に基づいて以下のように選択されます。
x | y | 加算対象データ |
---|---|---|
0 | 0 | Aレジスタ |
1 | 0 | Bレジスタ |
0 | 1 | 入力ポートの値 |
1 | 1 | ゼロ |
加算
選択したデータに、命令の下位4ビットの値を加算します。
加算した結果のキャリービットをフラグレジスタに格納します。
加算結果の格納
命令のデータおよび(「加算」により更新する前の)フラグレジスタの値についてビット演算を行い、以下の式が0になるとき対応するレジスタに加算した結果(の下位4ビット)を格納します。
式 | 格納先 |
---|---|
命令の6ビット目 OR 命令の7ビット目 | Aレジスタ |
(NOT 命令の6ビット目) OR 命令の7ビット目 | Bレジスタ |
NOT ((NOT 命令の6ビット目) AND 命令の7ビット目) | 出力ポート |
NOT (命令の6ビット目 AND 命令の7ビット目 AND (命令の4ビット目 OR (NOT フラグレジスタの値))) | 出力ポート |
プログラムカウンタの更新
「加算結果の格納」においてプログラムカウンタに加算結果を格納しなかった場合、プログラムカウンタの値を1だけ増やします。
1増やすと16になる場合は、0にします。
決まったペースで実行する
1Hzなどの決まったペースでステップを実行するため、「画面更新時実行」を用いて定期的に以下の処理を行うようにしました。
- 現在時刻を取得する
- 現在時刻が次の実行予定時刻以降であれば、ステップを実行し、次の実行予定時刻を実行間隔だけ増やす。
すなわち、単純化すると以下のような処理です。
以下のサンプルでは、カウントを1秒に1ずつ増やします。
カウントは0。
カウンタ表示はカウントのラベル作成。
カウント間隔は1000。
次カウント時刻はシステム時間ミリ秒にカウント間隔を足す。
●カウント処理とは
現在時刻はシステム時間ミリ秒。
次カウント時刻が現在時刻以下の間、繰り返す
カウントを1だけ増やす。
次カウント時刻をカウント間隔だけ増やす。
ここまで。
カウンタ表示にカウントをテキスト設定。
「カウント処理」を画面更新時実行。
ここまで。
「カウント処理」を画面更新時実行。
プログラム
前半でUIを作成し、後半で動作を定義する形で実装しました。
プログラムは貯蔵庫に置きました。
TD4エミュレータ
ハマったポイント
「DOM子要素全取得」が配列を返すというのは嘘
なでしこさん マニュアル - plugin_browser/DOM子要素全取得
には、
DOMの要素PAの子要素をクエリqを指定して結果を全て取得して配列で返します。
とあります。
しかし、実際に返されるのは配列ではないようです。
その証拠に、プログラム
3回繰り返す
回数のチェックボックス作成。
ここまで。
チェックリストはDOM親要素の「input[type=checkbox]」をDOM子要素全取得。
チェックリストの配列要素数を表示。
チェックリストを配列逆順。
を実行すると、「配列以外が指定されました」というエラーが出てしまいました。
「配列逆順」を使ったのに、使っていない「配列ソート」に関するエラーが出ているのでデタラメなのではないか…という気もしますが、「配列逆順」のかわりに「配列ソート」を使ってみても同じエラーが出ました。
少しプログラムを変えてみます。
3回繰り返す
回数のチェックボックス作成。
ここまで。
チェックリストはDOM親要素の「input[type=checkbox]」をDOM子要素全取得。
「console.log」を[チェックリスト]でJS関数実行。
以下の実行結果が得られました。
「DOM子要素全取得」は、実際には配列ではなくNodeListを返すようです。
「でないならば」はunlessではない
Perlの unless
のような構文として、「でなければ」があります。
もし、いいえでなければ
「にゃーん」と表示。
ここまで。
ここを「でないならば」と書いてしまうと、エラーになりました。
もし、いいえでないならば
「にゃーん」と表示。
ここまで。
[文法エラー]main.nako3(1行目): 『もし』文の条件で次のエラーがあります。 [文法エラー]main.nako3(1行目): 不完全な文です。単語『いいえ』、単語『ない』が解決していません。
if
に相当する「ならば」に引きずられているのですが、そもそも unless
には if
の i
も f
も含まれていないですし、これは完全にただの自分のミスですね。
FALSEはいいえと等しく無い
論理否定をしたい時、「いいえと等しい」はうまく動きませんでした。
もし、(1=0)がいいえと等しいならば
「等しい」と表示。
違えば
「×」と表示。
ここまで。
このプログラムは「×」を表示します。
「いいえ」は0と定義されています。
さらに、「偽」も0と定義されています。
false
を使いたければ、「FALSE」を使うのがいいようです。
もし、(1=0)がFALSEと等しいならば
「等しい」と表示。
違えば
「×」と表示。
ここまで。
このプログラムは「等しい」を表示します。
「=」を使えば、FALSEといいえの比較がTRUEになるようです。
もし、(1=0)=いいえならば
「イコール」と表示。
違えば
「×」と表示。
ここまで。
このプログラムは「イコール」を表示します。
ちなみに、論理否定は「論理NOT」でできるようです。
この「論理NOT」の解説には、
値Vが0ならば1、それ以外ならば0を返します。
と書かれています。
しかし、実際はFALSEは0と等しく無い、すなわち0以外であるにも関わらず、値VがFALSEのときも1を返しました。
また解説に嘘が書いてありましたね。
もし、FALSEが0と等しいならば
「前提不成立」と表示。
違えば
もし、論理NOT(FALSE)が0と等しいならば
「解説通り」と表示。
違えば
「嘘つき」と表示。
ここまで。
ここまで。
存在しない関数がなぜか動くように見えることがある
定義したのは「レジスタ読取」なのに、間違って「レジスタ取得」と書きました。
「レジスタ取得」はありません。
●(レジスタを)レジスタ読取とは
レジスタを戻す。
ここまで。
フラグレジスタは1。
(フラグレジスタをレジスタ取得が0と等しい)を表示。
このプログラムはなぜか動作し、「false」が表示されました。
フラグレジスタを1から0にしても「false」が表示されたので、期待通りには動いていないようです。
「0と等しい」を外すと、エラーになりました。
コンパイルエラーや異常終了を起こさないが期待した動作にならないタイプのバグは厄介ですね。