4.1 背司 用いられるアセンプリのコマンドについて説明を行う。 4.1.3 コマンド 算術演算と論理演算 ADD R2 ′ RI ′ R3 ADD R2, RI ′ f00 AND RI ′ RI ′ R2 63 どのようなコンピュータであれ、加算 / 減算のような基本的な算術演算や、ビットシ フトやビット単位の否定のような基本的な論理演算を実行できることが求められる。 こでは、 一般的な機械語のシンタックスで書かれた例をいくつか示す。 / / RI ←「 RI と R2 のビット単位 And 」 / / そのラベルが指す位置にあるメモリの値が f00 の値である ) / / R2 ← RI + f00 ( f00 はユーザーが定義したラベルであり、 / / R2 ← RI + R3 ( RI 、 R2 、 R3 はレジスタ ) メモリアクセス よって行う。 レスを指定する、もしくはシンポルを用いて特定のアドレスを参照することに メモリのアドレスを指定する最も一般的な方法は、次に示すように、直接アド 直接アドレッシング (direct addressing) シングモードはほとんどの場合サポートされていることだろう。 タが異なれば、その作法や表記法も異なる可能性があるが、次に示す 3 つのアドレッ 要求されたメモリのワードに対して、そのアドレスを指定する方法である。コンピュー グモード (addressingmode) を用いていることだろう。アドレッシングモードとは、 これらのメモリアクセスを行うコマンドは、いくっかの種類からなるアドレッシン でデータを移動するように設計されている。 み込みや格納を行うコマンドを備えており、それらのコマンドはレジスタとメモリ間 を行う場合である。どのようなコンピュータであっても、メモリに対して明示的に読 も操作を行う。ふたつ目は、メモリに対して明示的に読み込み (load) や格納 (store) や論理演算はレジスタに対して操作を行うだけではなく、特定のメモリ位置に対して とつ目は、ちょうど先ほど見たように、算術演算や論理演算の場合である。算術演算 メモリアクセスを行うコマンドは次に示すふたつのどちらかの場合に該当する。ひ
5.2 Hack ハードウェアのプラットフォーム仕様ー 99 キーボードの出力 newline backspace left arrow uparrow rightarrow downarrow home 134 133 132 131 130 129 128 図 5 - 4 Hack プラットフォームの特別なキー end pageup p agedown insert delete e,SC fl ー f12 キーボードの出力 135 136 137 138 139 140 141 ー 152 こまででデータメモリの内部バーツについて個別に説明した。それではデータメ モリ全体のアドレス空間について説明しよう。 メモリ全体 これらのモジュールは、図 5-5 に示すように 3 つのセクションに分けられた単一のア データストレージ ) に加えて、スクリーンとキーポードのメモリマップが含まれる。 は Memory と呼ばれる回路によって提供される。このメモリ回路には RAM ( 通常の Hack プラットフォームのアドレス空間全体 ( つまり、その全体のデータメモリ ) ドレス空間に存在する。 回路名 入力 コメント 出力 関数 Memory in [ 16 ], load ′ address[151 / / out [ 1 6 ] メモリアドレス空間全体 / / 何を書き込むか / / 書き込み有効ビット どこに書き込むか / / 与えられたアドレスにおけるメモリの値 1. out (t)=Memory[address(t) ] (t) 2. もし load ( t ー 1 ) が 1 であれば、 Memory[address(t—1)] に ) = in に一 1 ) (t は現在のタイムユ二ットである ) 24576 (Ox6000) より大きいアドレスは無効。 16384 (Ox4000) から 24575 (Ox5FFF) の範囲のアドレスはスクリーンのメモリマップにアクセスする。 24576 (Ox6000) はキーボードのメモリマップにアクセスする。これらのアドレスについ ての仕様は Sc 「 een と Keyboa 「 d 回路の仕様で述べた。
74 ー 4 章機械語 A レジスタを使用する場合、移動を伴う C 命令 (j ビットが 0 でない場合 ) において は M を参照すべきではない、と言える ( その逆も同様 ) 。 424 シンボル アセンプラによるコマンドは、定数もしくはシンポル (symbol) を用いてメモリ位 置 ( アドレス ) を参照することができる。シンポルは、アセンプリプログラムにおい て次の 3 つの方法によって用いられる。 定義済みシンボル RAM アドレスの特別なものについては、どのようなアセンプリプログラムからで も、次に示す定義済みシンポルを用いて参照することができる。 仮想レジスタ アセンプラによるプログラミングを単純化するために、 R0 から RI 5 までのシ ンポルが RAM アドレスの 0 から 15 をそれぞれ参照するように、あらかじめ 定義されている。 定義済みポインタ sp 、 LCL 、 ARG 、 TH 工 s というシンボルは RAM アドレスの 0 から 4 をそれぞ れ参照するように定義されている。こで、これらのメモリ位置に対してふた うつのラベル名が与えられていることに注意されたい。たとえば、アドレスの 2 番目の位置は R2 もしくは ARG というシンポル名で参照することができる。 このように命名した理由は「バーチャルマシン」を扱う 7 章と 8 章で明らかに なる。 入出力ポインタ ( レ 0 ポインタ ) SCREEN と KBD というシンポルは、 RAM アドレスの 16384 ( 0X4000 ) と 24576 ( 0X6000 ) をそれぞれ参照するように定義されている。これらのアドレスはス クリーンとキーポードのメモリマップにおけるべースアドレスを示す。 ラベルシンボル これはユーザーが定義するシンポルであり、 goto コマンドの行き先のラベルとして ( xxx ) " という形式のコマンドで宣言される。このコ 用いられる。このシンポルは、
106 ー 5 章コンピュータア 54 展望 ーキテクチャ 本書の方針に従い、 Hack コンピュータのアーキテクチャは必要最小限にとどめて いる。一般的なコンピュータであれば、より多くのレジスタを持ち、 ALU も性能が良 く豊富な命令セットを持つ。しかし、その違いは主に「量」に関する点である。「質」 の点から見れば、 Hack はほとんどすべてのコンピュータと同じであり、同じ概念上 の枠組みであるノイマン型アーキテクチャに従う。 機能的な点から見れば、コンピュータは次のふたつに分類することができる。ひと つは、汎用コンピュータ (general-purpose computer) であり、それはプログラム の差し替えが容易に行えるように設計されている。もうひとつは、専用コンピュータ (dedicated computer) であり、ゲーム端末やデジタルカメラ、武器システムや工場 設備などのように他のシステムに埋め込まれたコンピュータである。この専用コン ピュータでは、どのような用途であれ、単一のプログラムが専用の ROM に書き込ま れており、それが唯一実行されるプログラムである ( たとえば、ゲーム端末の例では、 ゲームソフトは外部のカートリッジに存在し、このカートリッジは交換可能な ROM にすぎない ) 。以上の点を除けば、汎用コンピュータと専用コンピュータはアーキテク チャについて同じ構造ーー格納プログラム、「フェッチ / デコード / 実行」のロジック、 CPU 、レジスタ、プログラムカウンタなど に基づいている。 ほとんどの汎用コンピュータは、 Hack とは違って、データと命令の両方を格納す るために単一のアドレス空間を用いる。そのようなアーキテクチャでは、命令によっ て指定される命令用アドレスとデータ用アドレスは同じ場所 ( 共有アドレス空間のた めの単一のアドレス入力 ) に送信されなければならない。明らかにこれを同時に行う ことは不可能である。一般的な解決策は 2 回分のサイクルによる実装をベースとする ものである。まず「フェッチサイクル」において、メモリのアドレス入力に命令アド レスが送られる。それによって即座に現在の命令が発信され、その命令が命令レジス タに格納されることになる。それに続く「実行サイクル」では、命令がデコードされ、 メモリのアドレス入力にデータアドレスが送られ、選択されたメモリ位置に対して操 作を行う ( この操作はオプションとして用いることができる ) 。一方、 Hack アーキテ クチャはアドレス空間がふたつに分離されている。それによって 1 サイクルでフェッ チと実行の両方を行うことができる。その点が Hack コンピュータの特徴である。し かし、ハードウェア設計をそのように単純化することには代償がつく。その代償とは 動的にプログラムを変更できないことである。 I / O に関して言うと、 Hack のキーポードとスクリーンは簡素なものである。汎用
に制限を受けることなく、書き込み / 読み込みができる、ということから来ている。 まり、メモリ中のすべてのワードは一一その物理的に存在する場所に関係なく じ時間で直接アクセスできなければならない。 てつ 同 out (word) (word) address (O から n-D load Register 0 Register 1 Register 2 Register n—l RAMn 直接アクセスロジック 図 3-3 ( 概念上の ) RAM 回路。 RAM の幅と長さは変えることができる RAM のどのレジスタにアクセスするかを指定する。メモリ操作が読み込みの場合 ドレス入力、ロードビットの 3 つである。アドレス入力によって、現時刻において、 以上をまとめると、 RAM は次の 3 つの入力を受け取る。それは、データ入力、ア より、論理的な意味でのアドレスが実現される。 が、 RAM 回路は「直接アクセスロジック (direct accesslogic) 」を備える。これに レスによってレジスタの物理的な位置は決定されない。これは後ほど見ていくことだ 物理的な意味でのアドレス ( 所在地 ) ではない、ということである。そのため、アド こで注意してほしいことは、「アドレス」とは ことができる論理ゲートを構築する。 るのに加えて、ツという数に対して、アドレスがツ番目のレジスタを個別に選択する までの間の整数 ) をアドレスとして割り当てる。次に、れ個のレジスタ配列を構築す スタにより構成される ) に対して、他とは重複しないユニークな番号 ( 0 かられ一 1 そのような要求を満たすためには、最初に、 RAM の各ワード ( これはれ個のレジ
6.4 展ⅵ 127 含むようにシンポルテープルを初期化する。 1 回目のバス アセンプリプログラム全体を 1 行ごとに見ていき、コードを生成せすに、シンボル テープルだけを作成する。プログラムを 1 行ごとに読んでいく過程で、現在の命令が 読み込まれる ROM アドレスの番号を保持する。このアドレス番号は 0 から始まり、 (Xxx) のような擬似コ c 命令または A 命令に出くわすたびに 1 だけ加算していく。 マンドに出くわした場合は、 xxx をプログラムの次のコマンドが格納される ROM ア ドレスに対応づけてシンポルテープルに追加する。このパスによって、プログラムの すべてのラベルはその ROM アドレスへと対応づけすることができる。変数について は 2 回目のパスで扱う。 2 回目のバス 再度プログラム全体を見ていき、 1 行ごとにパース処理を行う。 A 命令のシンポル に出くわしたとき、つまり、 @Xxx という命令において xxx が数値でなくシンポルで あるとき、シンポルテープルで xxx という名前のシンボルを探す。もしそのシンポル が見つかれば、そのシンポルを対応する数値に置き換え、コマンドの変換は完了とな る。もしテープルでそのシンポルが見つからなければ、それは新しい変数を表してい ることになる。これに対処するために、 ( xxx n ) というべアデータをシンボルテー 。こで、 , 。は次に使うことカ : できる RAM アドレスの番号である。 プルに追加する これにてコマンドの変換作業は完了となる。割り当てる RAM アドレスは連続する番 号であり、 16 から始まる ( 定義済みシンポルが割り当てられたアドレスの直後 ) 。 これでアセンプラの実装は終わりである。 6.4 展望 多くのアセンプラと同様に、 Hack アセンプラも比較的単純なプログラムであり、主 に行うことはテキスト処理である。当然ながら、機械語の種類が多くなればなるほど、 機械語のためのアセンプラは複雑になる。また、 Hack にはない、より洗練されたシ ンポル対応を行うアセンプラも存在する。たとえば、特定のデータアドレスを明示的 に指定することができるアセンプラなどが挙げられる。そのようなアセンプラであれ ば、たとえば、「 table + 5 」というコマンドを用いて、 table によって参照されるア さらに、多くのアセンプラは ドレス位置から 5 番目の場所を参照することができる。
6.1 シンボル 1 13 月 . 新艮 バイナリ命令はバイナリコードによって表現される。当然ながら、メモリアドレス を参照するには実際の数字を用いる。たとえば、物の重さを表すために we i ght と いう変数があり、それを用いるプログラムがあるとしよう。この変数はコンピュータ のメモリアドレスが 7 に位置する値を参照しているとする。バイナリコードのレベル では we 主 ght 変数を操作する命令は、 7 というアドレスを明確に書かなければなら ない。しかし、アセンプリの世界では、「 L 〇 AD R3 ′ 7 」というコマンドの代わりに 「 LOAD R3 , we ight 」というコマンドを書くことができる。両方のコマンドにおい て、「 R3 に Memory [ 7 ] の値を設定する」という同じ命令を実行することには変わり ない。この例と同様に、「 g 〇 t 〇 250 」というコマンドの代わりに、アセンプリ言語で は「 g 〇 t 〇 L 〇〇 p 」というようなコマンドを書くことができる。 LOOP というシンポ ルは、アドレスが 250 の位置を参照するようにプログラムのどこかで設定されている ことを前提としている。アセンプリプログラムでシンポルを使用するケースは一般的 に次のふたつの場合がある。 変数 プログラマーはシンポルによる変数名を使うことができる。変換器が " 自動で " こで注意すべき点は、プログラムの その変数にメモリアドレスを割り振る。 変換を通じて各シンポルが同じアドレスに割り当てられるかぎり、実際のメモ リアドレスの値は重要ではない、ということである。 ラベ丿レ プログラマーはプログラム中のさまざまな位置をシンボルによって印付けする ことができる。たとえば、 LO 〇 p というラベルを用いて、あるコード領域の最 初の場所を指すように宣言することができる。そうすれば、プログラムの他の コマンドにおいて「 got 〇 L 〇〇 p 」のように書くことができる。 アセンプリ言語にシンポル機能を取り入れるということは、アセンプラを単純なテ キスト処理のプログラムから、より洗練されたプログラムへと進化させなければなら ない、ということを意味する。確かに、「決められた記号」をそれに対応するバイナリ へと変換する処理は、そう難しくはない。しかし、ユーザーの定義した変数名やラベ ルを実際のメモリアドレスへ対応づける処理は難しい問題である。実際、この「シン ポル解決」は、ハードウェアのレベルからソフトウェア階層へと登る歩みにおいて、
ーム仕様ー 95 Hack ハードウェアのプラットフォ 5.2 回路名 CPU / / M 入力値 (M は RAM[A] の値 ) 入力 inM [ 1 6 ], / / 実行する命令 土 n s t ru ct 土 0 n [ 16 / / 現在のプログラムを再実行するか (reset=l) 、 / / そのまま続けるか (reset=0) を指定する信号 / / M 出力値 outM [ 1 6 ], / / M に書き込みを行うか ? writeM, / / データメモリ中の M のアドレス addressM[15], / / 次の命令のアドレス pc [ 15 ] Hack 機械語の仕様に従って命令が実行される。言語仕様にある D と A は CPIJ に 存在するレジスタを指し、一方、 M はアドレスが A の場所にあるメモリの値である (inM がその値を保持している ) 。 M に値を書き込む必要がある場合は writeM が 1 となる。その場合、書き込まれた値 は outM にも送信され、そのアドレスは addressM にも送信される (writeM=0 の場合、 outM にはいかなる値も現れる可能性がある ) 。 reset=l の場合、 CPIJ は命令メモリのアドレスが O の場所に移動する ( つまり、 次の時間サイクルにて pc=0 に設定される ) 。 reset=0 の場合は、現在命令の実行 結果に従って移動先のアドレスが決まる。 出力 関数 inM ァータメモリから outM writeM addressM 16 ァータメモリへ っ dO instruction 1 命令メモリから 1 6 PC 命令メモリへ re S et 1 CPU : 2 章と 3 章で作成した ALU とレジスタから作られる 図 5 -2 5.2.3 命令メモリ Hack の命令メモリは直接アクセスできる読み込み専用のメモリ装置であり、 ROM
5.1 アドレスレジスタ (addressing register) 月 . 示、 データを読み書きするために、 CPU は常にメモリにアクセスする必要がある。 メモリアクセスの伴う命令はすべて、メモリのどのワードにアクセスするかと いうことを、つまりは、「アドレス」を指定しなければならない。このアドレス は、現在の命令の中に含まれるかもしれない。または、ひとつ前の命令の実行 結果に依存して決定されるかもしれない。後者の場合、アドレスの値は専用の レジスタに格納される ( その値が後ほどメモリアドレスとして扱われる ) 。この レジスタがアドレスレジスタである。 プログラムカウンタレジスタ (program counter register) プログラムを実行するとき、命令メモリから次にフェッチすべき命令のアドレ スについて、その経過を CPU は追う必要がある。このアドレスは、「プログラ ムカウンタ」または「 PC 」と呼ばれる特別なレジスタに保持される。 PC には アドレスが格納されており、このアドレスは命令メモリからフェッチを行うア ドレスである。そのため、現在の命令を実行する過程で CPU は PC を更新す る。この更新作業には次に示すふたつの場合が存在する。 現在の命令に「 g 〇 t 〇」命令が含まれなければ、プログラムの次の命令を指 すように PC の値はインクリメントされる。 現在の命令に「 g 〇 t 〇 n 」命令が含まれれば、 CPU は PC に n を書き込む。 5 亠 6 入出力 コンピュータの外にある環境とインタラクテイプにやりとりするために、コンピュー タは I / O デバイス ( 入出力装置 ) を用いる。これには、スクリーン、キーポード、プ リンタ、スキャナ、ネットワークカード、 USB デバイスなどが含まれる。もちろん、 この他にもたくさん存在する。たとえば、自動車や兵器システム、医療装置などをコ ーでは、そのよう ントロールするための専用デバイスなど、挙げればきりがない。こ なさまざまなデバイスに対して詳細な分析は行わない。それにはふたつの理由がある。 ひとつ目は、どのようなデバイスであれ、それぞれに独自の機構を持ち、独自の技術 知識が必要になるからである。ふたつ目は、すべてのデバイスがコンピュータにとっ てまったく同じに見えるような仕掛けが存在するからである。この技法に関して最も 単純な仕掛けはメモリマッブドレ 0 (memory-mapped I/O) である。 メモリマッブド I/O の基本的なアイデアは、 I/O デバイスのための領域をメモリ 上に割り当てることである。そうすることで、 I/O デバイスを CPU にとっては通常
306 い 2 章オペレーティンクシステム を 181 までに制限することで、この問題を避けることができる。 12.3.6 Keyboard Keyboard. keyPressed() ワードである。このワードのアドレスは 24576 である。 Hack プラットフォームにおいて、キーポードのメモリマップは単一の 16 ビット ドレスが 0 + j である位置を操作する命令をコンパイラが生成することになる。 定 ( または取得 ) するには、 memory[j] を操作すればよい。そうすれば、 RAM ア の最初のアドレスを指すことになる。 RAM で物理的なアドレスが j の位置に値を設 図 12-16 の最初の 2 行に従えば、 memory 配列のべースはコンピュータの RAM 示す。 ら、コンピュータメモリ全体に直接アクセスすることができる。図 12-16 に詳細を の定数値はメモリのアドレスとして扱われる。特に、参照変数が配列であったとした Jack 言語は、プログラマーが参照変数に定数値を設定することを禁止していない。こ その仕掛けを実現するためには、参照変数 ( ポインタ ) を使用する。仕様として、 ができる。 単純な Jack プログラミングを用いて、 peek と poke を実装することで実現すること タのメモリを完全に操作できる仕組みが Jack 言語には備わっている。この仕掛けは、 実現するにはどうすればよいだろうか ? 結論からいうと、プログラマーがコンピュー このファンクションはメモリへの直接アクセスを行う。これを高水準言語のレベルで Memory. peek() 、 Memory. poke() 12-3.7 Memory 整を行ったアクセス ) を行う。そのレシピはそれぞれ図 12-14 と図 12-15 で与えた。 このファンクションは、単一の文字と文字列入力への " 調理された " アクセス ( 調 Keyboard. readChar() 、 Keyboard. readString() 行うだけである。 Memory. peek() を用いて簡単に実装することができる。 このファンクションはそのメモリアドレスに " 生の " アクセス ( 直接アクセス ) を