オール・トランジスタ4ビットCPUの製作とFPGA開発
[Vol.1 ノイマン型CPUの設計]

ALU,レジスタ,I/Oなどをトランジスタ・レベルで手作りし,さらにFPGAにも実装


【Index】

Vol.1 ノイマン型CPUの設計

Vol.2 CPUのレジスタとI/Oの設計

Vol.3 Lチカで学ぶFPGA開発体験

Vol.4 CPUのROM,PC,ALUの設計

Vol.5 ステート・マシンと命令デコーダの設計

Vol.6 CPUの全体統合とプログラムの実行

1.フルディスクリートのシンプルなCPUを作る

FPGA入門の第一歩!コンピュータの心臓部を手作りする

1738個のMOSFETで作る

この技術解説シリーズでは,コンピュータの心臓部である「中央処理装置」(central processing unit:CPU)を実際に作りながら,その動作原理を学んでいきます(Vol.1~6の全6回を予定).

写真1に示すのは,これから作るCPUをキット化した「CPU1738」です.このCPUキットにはICをいっさい使っていません.すべての回路は個別(ディスクリート)部品のMOSFET(Metal Oxide Semiconductor Field Effect Transistor:MOS型電界効果トランジスタ)だけで構成されています.合計1738個のMOSFETを使って,コンピュータとしての動作を実現しています.

具体的な組み立て方法などは商品のマニュアルを参照してください.

写真1 オール・トランジスタ製!フルディスクリートCPU自作キット「CPU1738」(設計・製作:別府 伸耕 リニア・テック)
ICはいっさい使わない!1738個のMOSFETでCPUに必要なすべての回路を作る

FPGAを使った設計例も解説

このCPUの回路は単純なものですが,本質的な部分は一般的なCPUやマイコンと同じです.また,すべてトランジスタで構成されているためブラック・ボックス的な部分がまったくありません.これから解説する一連の内容を理解すればCPUやマイコンを応用した製品設計やプログラム開発において大いに役立つ知識が得られます.

本技術解説シリーズの主旨は「トランジスタだけで構成されたCPUを作ること」ですが,それと平行してFPGA上に同じ機能をもったCPUを実装する方法も解説します.使用するFPGAや開発環境についてはコラムを参照してください.

ディジタル回路ブロック

MOSFETだけで論理ゲートを構成した基板

「内部構造がすべて見えるCPU」を実現するために,写真2に示す「ディジタル回路ブロック」を用意しました.このブロックの基板にMOSFETをはんだ付けすると,トランジスタ・レベルでCMOSのディジタル回路を構築することができます.信号ラインにはLEDが付いているので,すべての信号線の状態を一目で確認できます.

ディジタル回路ブロックは全部で10種類あり,1つ1つのブロックは「ANDゲート」,「ORゲート」,「NOTゲート」といった基本的な論理ゲート回路として動作します.これから作るCPUは,このブロックを組み合わせて作ります.

写真2 フルディスクリートCPUはミニ・モジュール「ディジタル回路ブロック」を組み合わせて作る(MTG-2005-SET)
MOSFETでできた全10種,合計24個の論理ゲートを組み合わせていく

CPUは336個の論理ゲートで構成されている

これから作るCPUで使う半導体部品は,MOSFETとダイオード(LEDも含む)のみです.使用するMOSFETの数は先に触れたとおり「合計1738個」です.また,使用するディジタル回路ブロック(論理ゲート回路)の数は「合計336個」です.に,使用する論理ゲートの内訳を示します.

表1 オール・トランジスタ製4ビットCPUの製作に使った論理ゲートの種類と個数
左はシールドを外したところ,右はシールドを取り付けたところ

CPUの設計方針

これから作るCPUの設計方針を次のように定めます.

  1. 「ディジタル回路ブロック」の論理ゲートだけを使う
  2. 処理の効率よりも回路のわかりやすさを優先する
  3. トランジスタ数をできる限り減らす

もともと「ディジタル回路ブロック」を作ったのは,「LEDの光で動作が見えるCPUを作りたい」,「すべての回路構造がまる見えのCPUを作りたい」というモチベーションがあったからでした.そのため,上記の方針(1)はこの企画の大前提だと言えます.

また,これからCPUを自作することで到達すべき目標は「動作原理をとことん理解すること」です.今回は「超高速で高信頼性を誇るCPUを製品化して大金を稼ぐ」とか「革新的な回路構成を提案して特許を取得する」といったことは目的としていません.この姿勢を明確にするために,方針(2)を加えました.

なお,私がこのCPUを試作したときはすべての部品を手作業ではんだ付けしました.これはちょっとだけ大変な作業だったので(とても肩が凝って視力が少し落ちました),設計中に上記の方針(3)を定めました.基本的にはトランジスタ数を減らす努力をしていますが,方針(1)および方針(2)のほうが優先準位は上です.回路のわかりやすさを優先して,あえてトランジスタ数が増えるような設計をしている箇所もあります.

“CPU1738”の各ロジック基板の動作を1つずつ解説していく

これから作るCPUは,コンピュータの動作に必要な最小限の要素で構成されています.

この設計にもとづいて作られたCPUキット「CPU1738」は合計8枚の基板で構成されており,各基板が1つの機能ブロックとして独立しています.そこで,1つ1つの基板ごとに実際の設計手順を追いながら回路図とその動作について解説していくことにします.

図1に,本技術解説シリーズの流れを示します.最初にCPU全体の仕様を決めます.これは,アーキテクチャや命令セットを検討する作業になります.その後,「ROM」,「レジスタ」,「I/O」,「プログラム・カウンタ」,「ALU」,「ステート・マシン」,「命令デコーダ」といった各機能ブロックの設計を行います.基本的に,簡単な回路から難しい回路の順に解説していきます.最後にすべての回路を統合してCPUとしてまとめ上げます.

CPUの完成後は「足し算」,「引き算」,「掛け算」,「割り算」といった基本的な計算を実行してみます.さらにCPUにモータ・ドライバ回路を接続して,「自動走行ロボット」を作る実験も行います.「CPU1738」キットには,これらの実験に必要な部材がすべて含まれています.

図1 本技術解説シリーズのストーリ(Vol.1~6の全6回を予定)
左はシールドを外したところ,右はシールドを取り付けたところ

コラムA 使用するFPGAと開発環境

使いやすいFPGAの“MAX10”を使って実験する

本技術解説シリーズでは,設計したCPUの回路をFPGA上に実装する方法も解説します.

今回はインテル(旧アルテラ)社製の“MAX10”シリーズのFPGAである“10M08SAE144C8G”を使います.写真Aに,このFPGAが搭載されたFPGAボード“MAX10-FB”を示します.このボード上には48 MHzの水晶発振回路が実装されており,FPGA内部のロジック・エレメント数は8000です.一般的な信号処理などを行う製品開発に応用するには十分な性能であると言えます.

写真A 本連載で使用するFPGAボード“MAX10-FB”

使用するハードウェア記述言語および開発環境

FPGAの内部回路の設計には,「ハードウェア記述言語」(Hardware Definition Language:HDL)と呼ばれる言語が使われます.今回は,よく使われているHDLの1つである“Verilog HDL”(ヴェリログ・エイチ・ディー・エル)で開発を進めることにします.

また,FPGAの開発環境はインテル社から無償で提供されている“Quartus Prime”を使います.FPGAに関する情報をまとめて表Aに示します.

表A これから使うFPGAの型番および開発環境

書き込み回路

“MAX10”FPGAを開発するときは,FPGAにオブジェクト・ファイル(内部回路データ)を転送するための「書き込み回路」が必要です.

私はTerasic社製の書き込み回路“1-TB1”(写真B)を使っています.また,“MAX10-JB”(写真C)という書き込み回路キットも発売されていますのでお好みで選択してください.

写真B FPGA書き込み器“1-TB1”
写真C FPGA書き込み回路キット“MAX10-JB”

コラムB FPGAの実験用の基板の製作

回路図

図AにFPGAを使う実験用の回路図を示します.これから扱うFPGAの内容は,すべてこの回路上で動作させることを前提とします.なお,NOTゲートやシュミット・トリガ・インバータは「ディジタル回路ブロック」を使っても良いですし,市販の汎用ロジックIC(74HCシリーズなど)を使っても結構です.

以下,各スイッチの役割を簡単に説明します.“RESET”スイッチを押すとCPU全体がリセットされます(そのような動作を実現するようにFPGAの内部回路を定義します).

“AUTO/MAN”の切り替えスイッチでCPUのクロック・パルスを選択します.スイッチを“AUTO”側にすると発振回路の出力(約20Hz)がFPGAに入力されます.“MAN”(MANUAL)側を選択した場合は手動でクロックを入力できます.手動クロックは“CK”スイッチを使って入力します.

図A FPGA上でCPUの実験を行うための回路

FPGAのピン設定

図Aに示す回路におけるFPGAのピン設定リストを表Bに示します.開発環境“Quartus Prime”の“Pin Planner”画面でこのように設定します.

表B FPGA実験基板用のピン設定

FPGA実験回路の製作例

写真DにFPGA実験基板の製作例を示します.今回は論理ゲートとして「ディジタル回路ブロック」を使いました.回路図には示していませんが,実験に便利なように3.3Vの3端子レギュレータを実装しています.

写真D FPGA実験基板の製作例

2.CPUの仕様を決める

ノイマン型アーキテクチャ

CPUを1つのシステムとして見たとき,その構造を表現するために「アーキテクチャ」(architecture)という言葉が使われます.通常,アーキテクチャと言った場合は具体的な回路の詳細に踏み込まず,システムの「構成要素」とその「接続関係」だけを規定することが多いようです.アーキテクチャを説明するときは,ブロック図がよく使われます.

図2に示すのは最も一般的なコンピュータのアーキテクチャで,「ノイマン型コンピュータ」あるいは「ノイマン型アーキテクチャ」(Neumannrchitecture)と呼ばれています.ノイマン型アーキテクチャは「中央処理装置」(CPU),「記憶装置」(メモリ),「入出力装置」(Input/Output, I/O)の3要素から成り立っています.なお,この名称はジョン・フォン・ノイマン(John von Neumann:1903~1957)という数学者にちなんだものです.

あえて特殊なアーキテクチャを採用する理由もないので,これから作るCPUでは基本的なノイマン型アーキテクチャを意識して設計を進めることにします.以下,各要素の役割についておおまかに解説します.

図2 ノイマン型コンピュータの基本構成
CPU,I/O,メモリの3要素から成る.現在のプログラマブルなコンピュータの基本形

メモリ

一般的なコンピュータでは,記憶装置(メモリ)として“ROM”(read only memory)と“RAM”(random access memory)を組み合わせて使います.

ROM

“ROM”は電源をOFFにしても内容を保持できるメモリで,「不揮発性メモリ」(non-volatile memory)とも呼ばれます.ROMにはCPUが実行する命令が“1”と“0”の羅列として記憶されています.一連の命令をまとめたものを「プログラム」(program)といいます.

一般的なパソコンでは「ハード・ディスク」(hard disk)や「ソリッド・ステート・ドライブ」(solid state drive: SSD)がROMとして使われています.また,1チップ・マイコンの場合は内臓のフラッシュ・メモリがROMの役割を果たします.これから作るコンピュータでは「電源を切っても“1”か“0”の状態を保持する」という要求を満たす最も原始的な回路ということで,たくさんのスイッチを並べたものをROMとして使います.

RAM

“RAM”は電源をOFFにすると内容が消えてしまう「揮発性メモリ」(volatile memory)です.データ・アクセスが高速なので,一時的な演算データを格納する用途に向いています.

一般的なパソコンでは「ダブル・データ・レート方式の同期型ダイナミックRAM」(double data rate - synchronous dynamic random access memory:DDR-SDRAM)が使われています.パソコン関係で「メモリ」と言った場合,通常はこのDDR-SDRAMを指すようです.また,一般的な1チップ・マイコンでは「スタティックRAM」(Static RAM:SRAM)がRAMとして使われています.これから作るコンピュータではCPUの内部にある「レジスタ」(後述)だけを一時的なデータの保存用に使うので,一般的なコンピュータのRAMに相当する回路は接続しません.

CPU

CPUはコンピュータ・システムの中核を担う回路で,メモリ(ROM)に記憶されたプログラムにしたがってさまざまな処理を実行します.CPUの内部は「演算部」(arithmetic logic unit)と「制御部」(control unit)に分けられます.「演算部」はデータの演算や転送を行う回路です.また,「制御部」はプログラムを読み込んで解読したり,CPU全体の動作を管理したりする回路です.これらの回路の詳細については,これから実際に設計しながら解説します.

I/O

I/O(Input/Output,入出力装置)は,コンピュータ・システムと外部回路の間のデータの入出力を担います.一般的な入力装置としてはキーボード,マウス,カメラ,マイクなどが挙げられます.また,出力装置としてはディスプレイやスピーカなどがあります.

これから作るコンピュータでは,簡単な入出力ポートを用意してI/Oとして使います.ロボットのモータを動かすモータ・ドライバ回路なども,広い意味ではI/Oの一種であると考えられます.

製作するCPUの目標スペック

これからCPUの設計を始めるにあたって,おおまかな仕様を決めておきます.

  1. データ・バスは4ビット幅
  2. 割込み機能は実装しない
  3. 外部のRAMへのアクセスは行わない
  4. 1命令を2クロックで実行
  5. 動作周波数は20Hz程度(最大200kHz)
  6. 電源は乾電池2本
  7. すべての信号線を光らせる

CPUの回路規模

CPUの回路規模は,扱うデータのビット幅に大きく左右されます.回路規模が大きいCPUをトランジスタ・レベルで手作りするのは困難なので,これから作るCPUで扱うデータは4ビットとします.これに伴い,CPUが指定できるメモリ・アドレスは$2^4=16$番地までとなります.この程度の小さなアドレス空間では,「割込み」(interrupt)が発生したときに実行するプログラム(割込みハンドラと呼ばれる)を用意するのが困難です.そのため,今回は割込み機能を実装しないことにします.

また,一時的なデータの格納場所はCPU内部の「レジスタ」のみとし,外部のRAMは使用しないものとします.これにより,メモリ・アクセスに関係する回路を簡単にできます.

CPUの動作速度

一般的なCPUは,「クロック・パルス」(clock pulse)のタイミングにしたがって動作します.通常は,1つの命令を実行するために複数回のクロック入力が必要です.これから作るCPUでは,基本的に「2クロックで1命令を処理する」ように設計します.ただし,回路設計上の都合により,1クロックで処理される命令もいくつかあります.

CPUの動作速度は「目で追える」程度の“1命令/秒”から“10命令/秒”とします.なお,最高速度として200kHzのクロックでも動作することを確認しています(それ以上でも動作する可能性はありますが未確認です).

CPUの電源

今回のCPUの電源は,乾電池2本で動かすことを想定して2.4Vから3.3V程度とします.なお,3Vの電源を接続した場合の消費電流は500mA程度です.CPUの回路はすべてMOSFET(CMOS)で構成されているので本来は消費電流がとても小さいはずですが,今回作るCPUはすべての論理ゲートの入出力にLEDが付いているので,それなりに大きな電流が流れます.

信号線が光る

このCPUは,その動作に合わせてすべての信号線が光ります.一目で動作がわかります.むしろ,キラキラと輝きながら動作するCPUを見るために作ったと言っても過言ではありません.プログラムを実行させた時のようすは壮観です.何時間眺めていても飽きません.ぜひ,実際に製作してこの感動を味わってみてください(写真3).

写真3 フルディスクリート4ビット・コンピュータ「CPU1738]はプログラムを実行すると,計算の進行に合わせてたくさんのLEDが輝く

3.CPUのアーキテクチャを決める

必要な要素を付け足しながらアーキテクチャを完成させる

これからCPUの設計を進めるために,CPU全体のアーキテクチャを検討しておきます.今回は「全部トランジスタだけで手作りする」ことが前提なので,あまり大きな回路規模にはできません.そこで,CPUとして動作するために必要な要素は何であるかを考えながら,少しずつ要素を付け加えていくことにします.「CPUのブロック図を育てていく」という感覚でアーキテクチャの検討を進めていきます.

算術論理演算装置(ALU)

CPUの内部回路の花形

CPUが行う動作といえば,やはり「演算」です.ディジタル回路における代表的な演算といえば「加算」(足し算)があります.また,入力されたデータの“1”と“0”をすべて反転させる「NOT演算」(論理反転)や,データの内容を1ビットだけ横にシフトする「ビット・シフト」も演算の一種です.これらの演算を担当する回路のことを「算術論理演算装置」(arithmetic logic unit:ALU)といいます.通常,ALUは図3のような形で表記されます.ALUはCPUの演算部の中核を担う回路であり,「CPUの花形」です.

図3 “ALU”はCPUの中で算術・論理演算を担当する

今回は「加減算器」をALUとして使う

これから作るCPUでは,4ビット・データの「加算」と「減算」を実行できる「加減算器」をALUとして使うことにします.また,計算結果が“0”であることを外部回路に伝えるための「ゼロ・フラグ」(Zフラグ)と,計算結果が桁あふれ(繰り上がり)したことを示す「キャリー・フラグ」(Cフラグ)を保持する回路も用意します.今回は論理演算やビット・シフトといった機能は実装しません.

「ただの加減算器ではつまらない」と思われるかもしれませんが,プログラムの書き方によっては幅広い応用が可能です.たとえば,足し算を一定の回数だけ繰り返すプログラムを用意すれば「掛け算」を実行できます.また,結果が“0”になるまで(Zフラグが立つまで)引き算を繰り返し,実行した引き算の回数をカウントするプログラムを用意すれば「割り算」を実行できます.よって,このCPUは「加算」,「減算」,「乗算」,「除算」の四則演算をすべて実行できることになります.後で実際に乗算や除算を実行するプログラムを書いて,実験してみましょう.

なお,加減算器の回路の実体は組み合わせ論理回路です.また,フラグを保持する回路はフリップフロップです.

レジスタ(A REG,B REG)

2個のレジスタを用意する

CPUを実際に動かすときは,ALUに対して何らかのデータを入力します.ALUの入力が不安定だと使いづらいので,ALUに対して入力するデータを保持する回路が欲しくなります.このように,CPUの内部で一時的なデータを記憶するために用意された回路のことを「レジスタ」(register)といいます.

今回は,2つあるALUの入力のそれぞれに対して4ビット幅のレジスタを用意することにします.1つめのレジスタを「Aレジスタ」(A REG),2つめのレジスタを「Bレジスタ」(B REG)という名前にします.これらの回路の実体は,フリップフロップと組み合わせ論理回路です.

これから作るCPUに搭載するレジスタは2つだけですが,一般的なパソコン用のCPUや1チップ・マイコンにはたくさんのレジスタが用意されています.レジスタの数が多いほど複雑な処理を高速に実行できます.

バス

ALUによる演算の結果も,やはり「レジスタ」に記憶しておきたいところです.そこで,図4のようにALUの出力をAレジスタおよびBレジスタの入力に接続することにします.レジスタはクロック・パルスが入力された瞬間だけ記憶データの入力を受け付けるので,図4のような接続にしてもデータの信号がぐるぐると回り続けるような事態にはなりません.

図4ではALUの出力,Aレジスタの入力,Bレジスタの入力の3つが1つの配線(4ビット幅)を共有する形になっています.このように,複数の回路ブロックの間で共有される配線のことを「バス」(bus)といいます.特に,CPUが扱う演算データをやりとりするバスは「データ・バス」(data bus)と呼ばれます.

図4 ALUに入力するデータを保持する「レジスタ」を追加した

入出力回路(I/O)

シンプルなI/Oを実装する

一般的なコンピュータには,外部回路とデータのやりとりをする“I/O”があります.

今回はシンプルにALUの出力を外部回路に出力するための「出力ポート」(OUT PORT)と,AレジスタおよびBレジスタの入力に対して外部信号を接続する「入力ポート」(IN PORT)を用意することにします.出力ポートは単なる4ビットのレジスタなので,その実体は組み合わせ論理回路とフリップフロップです.また,入力ポートの実体は外部回路からの信号を波形整形するためのシュミット・トリガです.このようなI/Oの構成は,一般的な1チップ・マイコンの「汎用入出力」(general purpose input/output:GPIO)と同じです.

トライ・ステート・バッファで信号が衝突しないようにする

出力ポートと入力ポートを加えたアーキテクチャを図5に示します.出力ポートと入力ポートも,さきほどと同じ「4ビット・データ・バス」に接続しています.

図5では,「ALUの出力」と「入力ポートの出力」が衝突しているように見えます.このままでは回路として正常に動作しないと思われるかもしれませんが,心配はいりません.実際に回路を設計するときは,ALUおよび入力ポートの出力回路に「トライ・ステート・バッファ」を入れます.これでALUや入力ポートの内部回路をバスから電気的に切り離せるので,信号がぶつかり合うことはありません.

なお,今回は回路規模が小さくて済むという理由でトライ・ステート・バッファを使うことにしましたが,FPGAの内部回路や本格的なASIC設計を行う場合はマルチプレクサ(MUX)を使ってバスに接続する信号を選択することをお勧めします.

図5 外部回路とデータのやりとりをする「入出力ポート」を追加した

プログラム・カウンタ(PC)

ROMのアドレスを管理する回路

CPUはROMに格納されているプログラムにしたがって動作します.ROMの1番地の命令を読み込んで処理したら,次は2番地,続いて3番地といった具合に動作を進めていきます.このことから,ROMから読み出すデータのアドレスを指定するためには何らかの「カウンタ」が必要であると考えられます.このように,ROMのアドレスを管理するためにCPUに内蔵されたカウンタのことを「プログラム・カウンタ」(program counter:PC)といいます.

ジャンプ命令にも対応できるようにする

プログラム・カウンタは基本的に「ROMに出力するアドレスの値を1つずつ増やしていく」という動作をしますが,柔軟なプログラムを実現するためには「指定したアドレスにジャンプする」という動作が求められます.これは,プログラム・カウンタのデータを強制的に上書きする操作に相当します.このような「値を上書き可能なカウンタ」は「プリセッタブル・カウンタ」(presettable counter)と呼ばれています.

以上のことから,プログラム・カウンタとして4ビットのプリセッタブル・カウンタを実装することにします.なお,この回路の実体は組み合わせ論理回路とフリップフロップです.

図6に,プログラム・カウンタを加えたアーキテクチャを示します.プログラム・カウンタの出力はROMに接続します.また,プログラム・カウンタの入力にはALUの出力(データ・バス)を接続することにします.ALUの出力はREG AやREG Bなどのデータによって変更できるので,このような配線にしておけば最も柔軟にプログラム・カウンタの出力を変更できると考えられます.

図6 ROMのアドレスを管理する「プログラム・カウンタ」を追加した

ROM

アドレスは4ビット

ROMは,CPUが読み込む命令やデータを記憶する回路です.本来ROMは「CPU内部のアーキテクチャ」とはあまり関係ありませんが,これから作るコンピュータ・システムの一部なのでここで一緒に検討することにします.

今回作るCPUは4ビットなので,プログラム・カウンタの出力も4ビットとなります.これにあわせて,ROMのアドレス入力も4ビットとなります.

データは8ビット

ROMの「アドレス」は4ビットですが,ROMが記憶する「データ」のビット幅には特に制限がありません.そこで今回はROMのデータ出力を8ビット幅として,上位4ビットを「命令データ」,下位4ビットを「数値データ」として扱うことにします.データ形式については後であらためて検討しますが,命令データはCPUの動作を決めるもので,数値データはCPUが実行する演算の対象となるものです.

ROMを加えた全体のアーキテクチャを図7に示します.ROMのアドレス入力は,プログラム・カウンタの出力に接続されています.このように,アドレスの信号が流れる配線のことを「アドレス・バス」(address bus)といいます.

ROMの出力の「数値データ」の部分は,Bレジスタの出力と共通のデータ・バスに接続してALUに入力する形にしました.これでROMに保存したデータを直接使って演算できるようになります.また,例によってBレジスタの出力部分とROMの数値データ出力部分には「トライ・ステート・バッファ」を挿入して,出力がぶつかり合わないようにしておきます.

ROMの回路の実体は,「プログラム」を記憶するためのスイッチの集合体です.

図7 CPUが実行するプログラムを保存する“ROM”を追加した

命令デコーダ(ID)

CPUは,ROMに格納されている「命令データ」を読み込んで動作します.この命令データに応じてCPU各部の動作を管理する回路を「命令デコーダ」(instruction decoder:ID)といいます(図8).今回作るCPUの具体的な命令についてはすぐ後に検討します.

命令デコーダの実体は,設計者が決めた命令の通りに各部ブロックの動作モードを決めたりバスの切り替えを行ったりするための信号を発生する組み合わせ論理回路です.

図8 ROMから読み込んだ命令を解読する「命令デコーダ」を追加した

ステート・マシン

一般的なCPUでは,命令を実行するステップは何段階かに分かれています.CPUは「命令の読み込み」,「演算処理の実行」,「データの書き込み」といった各ステップの動作を,クロック・パルスに合わせて進めていきます.このとき,CPUの内部にはクロックにあわせて回路全体の動作を管理する司令塔が必要となります.この役割を果たすのが「ステート・マシン」(state machine)と呼ばれる回路です.

図9に,ステート・マシンを加えた全体のアーキテクチャを示します.CPUの動作はROMから読み出した命令によって変化するので,ステート・マシンは命令デコーダの出力を参照しています.また,ステート・マシンの出力はCPUのすべてのブロックに供給されますが,その経路を描くと図が煩雑になってしまうので図9では省略しています.

ステート・マシンの実体は,組み合わせ論理回路とフリップフロップです.

図9 CPU全体の動作を管理する「ステート・マシン」を追加した

アーキテクチャの完成

ここまでの内容をすべてまとめると,図10に示すアーキテクチャが完成します.このCPUは単純な構成ではありますが,CPUとして必要なすべての要素を備えています.今後はこのアーキテクチャをもとにして設計を進めていきます.

なお,ALUに対する入力経路には「マルチプレクサ」(multiplexer:MUX)を挿入して,“0”(4ビットすべてが“0”レベル)を入力できるようにしています.これは,「A REGの内容だけをALUから出力する(B REG側を“0”にする)」あるいは「B REGの内容だけをALUから出力する(A REG側を“0”にする)」とった動作を実現するためのものです.この機能は,後で出てくる「A REGの内容をOUT PORTに出力する」などの命令を実行する際に使われます.

図10 これから作るCPUのアーキテクチャ
CPUとして必要な要素をすべて備えている

コラムC 割込み機能を実現するアドレス保存回路「スタック・ポインタ」

今回のCPUには搭載されていませんが,「割込み機能」を実装する場合は「スタック・ポインタ」(stack pointer)という回路が必要になります.

動作中に割込みが発生した場合,CPUはそれまでの処理を中止して,あらかじめ用意されている「割込みハンドラ」(interrupt handler)あるいは「割込みサービス・ルーチン」(interrupt service routine:ISR)と呼ばれるプログラムにジャンプします.これは,プログラム・カウンタの内容を割込みハンドラの最初のアドレスに上書きする操作に相当します.

割込みハンドラの処理が完了したら,CPUは元の処理に復帰します.復帰するためには,もともと実行していたプログラムのアドレスを記憶しておく必要があります.このために用いられる「アドレスを保存する回路」が,スタック・ポインタです.なお,割込み処理の途中で他の割込みが発生する「多重割込み」にも対応するために,スタック・ポインタは最後に保存した値が最初に読み出される仕組み(これをスタックという)を備えています.

スタック・ポインタの実体はフリップフロップと組み合わせ論理回路です.

4.CPUの命令仕様を決める

CPUで扱うデータ・フォーマット

これから設計するCPUの「命令」(instruction)は,すべて4ビット幅とします.このコンピュータのユーザはCPUの命令を実行したい順番に並べてROMに保存し,「プログラム」を作ります.また,CPUが処理する「数値データ」も,命令と一緒にROMに保存しておきます.この数値データは,演算に使う数値やジャンプ先のアドレスなどが該当します.この数値データも4ビット幅とします.

これから作るCPUが扱うデータ形式を図11のように定めます.一般的に,CPUが一度に扱うデータをまとめて「1ワード」(1word)と呼びます.今回は8ビットが1ワードとなります.

上位4ビットはCPUが実行する命令に相当する部分で,この部分は「オペコード」(operation code, OP code)と呼ばれます.これに対して,下位4ビットは処理の対象となる数値データです.この部分は「オペランド」(operand)と呼ばれます.

図11 これから作るCPUが扱うデータ形式

命令セット・アーキテクチャ

これから作るCPUの命令は4ビット幅なので,$2^4=16$種類の命令を用意することができます.今回は一例として,表2に示す命令を実装することにします.次表に示すようなCPUの命令一覧のことを,「命令セット・アーキテクチャ」(instruction set architecture:ISA)あるいは単に「命令セット」といいます.

命令セット・アーキテクチャはCPUのハードウェア構成と密接に関係していますが,命令実行時の細かい挙動やバイナリとの対応付けは設計者がある程度自由に決められます.今回は,先に決めたアーキテクチャに含まれる各ブロックをできる限り活用するような命令セットにしてみました.

なお,今回は思いついた順番に命令を書き並べて,オペコードを“0000”,“0001”,“0010”…と割り振りました.命令デコーダの回路を最適化することを考えると,もっと効率的なオペコードの割り振りがあるかもしれません.しかし,今回は「命令表のわかりやすさ」を優先することにします.この後の設計では,この命令表のとおりの動作を実現するように命令デコーダやステート・マシンを設計していくことになります.

表2 これから作るCPUの命令セット・アーキテクチャ

機械語とニモニック

表2からわかるとおり,CPUが読み込む命令は“1”と“0”の羅列である「バイナリ・データ」(binary data)です.このように,CPUが直接理解できる命令データのことを「機械語」(machine code)あるいは「マシン語」といいます.

それぞれの命令(オペコード)には,“0000”から“1111”までのバイナリが対応しています.人間がプログラムを作るときは,バイナリよりも「人間の言葉」に近い表記があると便利です.そこで,1つ1つのバイナリに対して英語に似せたシンボルを付けることにします.このシンボルのことを,「ニモニック」(mnemonic)といいます.例えば,“0000”という命令は「Aレジスタに [Data] を格納(ロード)する」という意味なので,“LD”(loadの略)という二モニックを割り当てています.

アセンブリ言語とアセンブラ

“LD A, B”のように,二モニック(動詞に相当する)とオペランド(目的語に相当する)を合わせたものを「アセンブリ言語」(assembly language)といいます.また,このアセンブリ言語で書かれたプログラムのことを,「アセンブリ・プログラム」(assembly program)といいます.

アセンブリ言語と機械語はほぼ1対1で対応しているので,ソフトウェアなどを使って機械的に変換することができます.アセンブリ言語を機械語に変換するソフトウェアのことを,「アセンブラ」(assembler)といいます.また,アセンブラによって生成された機械語プログラムは「オブジェクト・コード」(object code)と呼ばれます.

高級言語とコンパイラ

複雑で長いプログラムをアセンブリ言語で書くのはとても大変な作業です.そこで,より「人間の言葉」に近い感覚で記述できるプログラミング言語が開発されました.これを「高級言語」(high-level programming language)といいます.高級言語の代表例としては,「C言語」や「Java」などが挙げられます.高級言語で書かれたプログラムをもとにして機械語コードを生成するソフトウェアとしては,「コンパイラ」(compiler)や「インタプリタ」(interpreter)といったものがあります.

今回はアセンブラやコンパイラといった補助的なソフトウェアは使わず,「すべて手作業」で機械語のプログラムを作成します.

各命令の概要

以下,各命令の二モニックと実行時の動作を簡単に説明します.命令実行時の細かい動作は,後で各部の回路を設計しながら考えます.

データ転送命令

「データ転送命令」は,レジスタ間およびレジスタとROMの間でデータをやりとりする命令です.

  • LD A, [Data]
    Aレジスタに,[Data] を格納します.
  • LD B, [Data]
    Bレジスタに,[Data] を格納します.
  • LD A, B
    Aレジスタに,Bレジスタの内容を格納します.
  • LD B, A
    Bレジスタに,Aレジスタの内容を格納します.

算術演算命令

「算術演算命令」は,ALUを利用して加算(ADD)と減算(SUB)を行う命令です.

  • ADD A, B
    Aレジスタの値とBレジスタの値を加算し,結果をAレジスタに格納します.
  • SUB A, B
    Aレジスタの値からBレジスタの値を減算し,結果をAレジスタに格納します.
  • ADD A, [Data]
    Aレジスタの値に [Data] を加算し,結果をAレジスタに格納します.
  • SUB A, [Data]
    Aレジスタの値から [Data] を減算し,結果をAレジスタに格納します.

データ入出力命令

「データ入出力命令」は,I/Oポートとデータのやりとりをする命令です.

  • OUT A
    OUTポート(レジスタ)に,Aレジスタの内容を格納します.
  • OUT B
    OUTポート(レジスタ)に,Bレジスタの内容を格納します.
  • OUT [Data]
    OUTポート(レジスタ)に,[Data] を格納します.
  • IN A
    Aレジスタに,INポートの内容を格納します.

分岐命令

「分岐命令」は,プログラム・カウンタの値を書き換える命令です.

  • JUMP [Address]
    プログラム・カウンタに,[Address] を上書きします.
  • JNC [Address]
    もしALUの「Cフラグ」(キャリー・フラグ,繰り上がりフラグ)が“0”ならば,プログラム・カウンタに [Address] を上書きします.
  • JNZ [Address]
    もしALUの「Zフラグ」(ゼロ・フラグ)が“0”ならば,プログラム・カウンタに [Address]を上書きします.

動作制御命令

「動作制御命令」は,CPUの全体動作に関連した命令です.

  • HALT
    直前の状態を保持したまま,CPUの全動作を停止します.CPUをリセットするまで,動作は再開されません.なお,“halt”という英単語は「ホールト」と発音します.

関連資料


(c)2020 Nobuyasu Beppu