オール・トランジスタ4ビットCPUの製作とFPGA開発
[Vol.2 CPUのレジスタとI/Oの設計]
ALU,レジスタ,I/Oなどをトランジスタ・レベルで手作りし,さらにFPGAにも実装
- 著者・講師:別府 伸耕/Nobuyasu Beppu (リニア・テック)
- 企画編集・主催: ZEPエンジニアリング株式会社
- 関連製品:[VOD/KIT]実習キットでできる!ラズパイPicoでマイコン入門
- 関連製品:[VOD/KIT]実習キットでできる!ラズパイPico×Wi-FiモジュールでIoT超入門
- 関連製品:[VOD/KIT]一緒に動かそう!Lチカから始めるFPGA開発【基礎編】
- 関連製品:[VOD/KIT]STM32マイコン&Wi-Fiモジュールで学ぶ C/C++プログラミング入門
- 関連製品:[VOD/KIT]実習キットで一緒に作る!オープンソースCPU RISC-V入門
- 関連製品:[VOD/KIT]Python×実習キット×スマホでできる!ESP32マイコン活用術
【Index】
1.全体に共通する回路設計の考え方
CPUに必要なブロックを1つずつ設計していく
Vol.2以降では,前回考えた4ビットCPUのアーキテクチャに含まれる“A REG”,“B REG”,“I/O”,“ALU”,“PC”,“ROM”,“ID”,“STATE MACHINE”といった各ブロックの設計を行います.今後の回路設計では「ディジタル回路ブロック」を使ったオール・トランジスタの設計例と,Verilog HDLを使ってMAX10 FPGAに実装する設計例の両方を示します.
今回は,すべての回路設計で共通する考え方を整理してから,比較的回路構成が単純な“A REG”,“B REG”,“I/O”を作ります.
一般的な回路設計の流れ
入出力仕様を決める
図1に,Vol.2以降で解説する回路設計における共通の流れを示します.
最初に,設計対象のブロック(あるいはモジュール)の入出力端子の仕様を決めます.これは,その回路を外部から見た場合のインターフェース仕様を決定する作業となります.
内部構成をブロック図で表す
続いて,回路の内部構成をおおまかに決めます.求められている機能を実現するために必要となるブロックを並べて,それぞれの接続関係を定義します.このようにして,内部構成を「ブロック図」の形で書いておくとその後の設計を進めやすくなります.
詳細設計を行う
トランジスタ・レベルあるいはゲート・レベルの回路設計
ブロック図ができたら,それを実現するための具体的な回路を設計します.この作業における設計の抽象度は,実際に使用する部品によって異なります.
本技術解説シリーズのテーマは「CPUをトランジスタだけで作ること」なので,「トランジスタ」という部品を最小単位として回路構成を考えるのが妥当だと考えられます.しかし,すべてのブロックをトランジスタ・レベルで設計すると回路図が膨大な量になって大変です.
そこで,今回は「デジタル回路ブロック」を最小単位として扱うことにします.これは,いわゆる「論理ゲート回路」のレベルでディジタル回路を設計することに相当します.
レジスタ・トランスファ・レベルの回路設計
本技術解説シリーズでは,FPGA上にCPUを実装する設計も行います.今回は,一般的に用いられる「レジスタ・トランスファ・レベル」(Register Transfer Level:RTL)の設計手法を採用します.これは,データを保持する回路である「レジスタ」を最小要素として回路構成を記述する方法です.
場面に応じて適切な抽象度で考えることが重要
図2に示すように,設計の抽象度は,「レジスタ・トランスファ・レベル > 論理ゲート・レベル >トランジスタ・レベル」の順に低くなります(具体的,物理的になる).
より抽象度が高い考え方として,回路構造には深入りせずに表面的な振る舞い(behavior)に着目して動作を考える「ビヘイビア・レベル」というものもあります.前回考えたCPUのアーキテクチャを考える作業は,ビヘイビア・レベルで各ブロックを扱っていました.このように,回路設計の段階に応じて適切な抽象度を選択すると作業の効率が上がります.
動作検証を行う
回路設計が完了したら,動作検証を行います.トランジスタ・レベルの設計をする場合は回路シミュレータを使っても良いのですが,今回はデジタル回路ブロックの動作(論理ゲート・レベルの動作)が保証されていると仮定して,回路をそのまま組んで実験することにします.
また,FPGAの場合はVerilogでテスト・ベンチを記述して,論理回路シミュレータ“ModelSim”でシミュレーションを実行します.具体的なシミュレーションの手順やModelSimの使い方は,「Vol.3 Lチカ・ロジックのFPGA実装とシミュレーション(mz-cpu1738-da3)」を参照してください.
2.「レジスタ」の原型となる回路:“D-FF”
記憶回路
入力された信号を記憶する回路のことを「記憶回路」といいます.また,記憶回路を必要なビット数だけ並べた回路は「レジスタ」と呼ばれます.レジスタは,これから作るCPUにおいて中心的な役割を担う回路の1つです.
ここでは,レジスタの原型となる「D型フリップフロップ」(以下“D-FF”)の動作と回路構成について簡単に確認しておきます.
D-FFの基本動作
図3にD-FFのシンボル,表1にD-FFの動作を表した特性表を示します.この回路は“$CK$”端子に印加される電圧が立ち上がるタイミングで,“$D$”端子に印加された電圧レベルを記憶します.記憶した結果は“$Q$”端子から出力されます.“$\overline{Q}$”端子からは“$Q$”端子の論理レベルを反転させた信号が出力されます.このような動作をするD-FFは,「ポジティブ・エッジ・トリガ型D-FF」と呼ばれます.
“$CK$”信号の立ち下がりで入力信号を記憶する「ネガティブ・エッジ・トリガ型D-FF」もありますが,今回は扱いません.
「ディジタル回路ブロック」を使ったD-FFの製作
ポジティブ・エッジ・トリガ型のD-FFを作る方法はいくつかありますが,ここではNANDゲートを使った図4の回路について考えることにします.この回路を「デジタル回路ブロック」で実際に組むと,写真1のようになります.2入力NANDゲートを5個,2入力NANDゲートを1個使っていて,合計トランジスタ数は26個です.
ポジティブ・エッジ・トリガ型D-FFの動作解説
“U5”と“U6”はSR-FFを構成している
NANDゲートを使ったポジティブ・エッジ・トリガ型D-FFの動作原理について考えます.
図4の回路の“U5”と“U6”は,ノード“$X$”および“$Y$”を入力端子としてもつ「セット・リセット型フリップフロップ」(SR-FF)を構成しています.“$X = 0$,$Y = 1$”のときは出力が“$Q$ = 1”となり,“$X = 1$,$Y = 0$”のときは“$Q = 0$”となります.また,“$X = Y = 1$”のときは現在の状態が保持されます.なお,“$X = Y = 0$”はその後の状態遷移が一意に定まらないので入力禁止とします.
入力が“$D = 0$”のときにクロック信号“$CK$”が立ち上がった時
図5(a)は,データ入力が“$D = 0$”の状態でクロック入力“$CK$”を“0→1”と変化させたときのようすを表しています.このとき,ノード“$X$”のレベルは“1”のままで,ノード“$Y$”のレベルが“1→0”と変化します.これにより,“U5”と“U6”で構成されたSR-FFの出力は“$Q = 0$”となって入力信号“$D$”の値が記憶されます.
クロック入力“$CK$”が“1”になった後は“U4”の出力が“1”に固定されるので,入力“$D$”を変化させても出力には影響しません.また,クロックを“1→0”と立ち下げたときはノード“$X$”および“$Y$”のレベルが共に“1”になるので,“U5”と“U6”で構成されるSR-FFは直前の状態をそのまま保持します.このようにして,「ポジティブ・エッジ・トリガ」の動作が実現されます.
入力が“$D=1$”でクロック信号“$CK$”が立ち上がった時
図5(b)は,入力端子を“$D = 1$”とした状態でクロック入力“$CK$”を“0→1”と変化させたときのようすを表しています.このとき,ノード“$Y$”のレベルは“1”のままで,ノード“$X$”のレベルが“1→0”と変化します.これにより,“U5”と“U6”で構成されたSR-FFの出力は“$Q = 1$”となって入力信号“$D$”の値が記憶されます.
クロック入力“$CK$”が立ち上がった後に“$D$”入力を変化させたときの挙動や,クロック入力“$CK$”を立ち下げたときの挙動は先に考えた“$D = 0$”の場合と同じです.
非同期リセット機能付きのD-FF
図6に示すD-FFは,リセット端子“$\overline{RST}$”をもっています.このフリップフロップの特性表を表2に示します.クロック入力“$CK$”に対する挙動は先に考えたD-FFと同じですが,“$\overline{RST}$”端子を“0”にすると強制的に出力が“$Q = 0$”となります.このように,クロック信号と同期せず,任意のタイミングでリセットができる機能を「非同期リセット」といいます.よって,この回路は「非同期リセット付きのポジティブ・エッジ・トリガ型D-FF」ということになります.
後で設計するCPUの「レジスタ」回路では,この「非同期リセット付きポジティブ・エッジ・トリガ型D-FF」を基本要素として使います.なお,リセット信号は“0”で有効になるので,リセット入力端子の名前は「負論理」であることを示す「バー」を付けています.
非同期リセット付きD-FFの製作例
NANDゲートを使って構成した「非同期リセット付きポジティブ・エッジ・トリガ型D-FF」を図7に示します.また,この回路を「デジタル回路ブロック」で実際に組んだものを写真2に示します.この回路では2入力NANDゲートを2個,3入力NANDゲートを4個使っており,合計トランジスタ数は32個です.
非同期リセット付きD-FFの動作解説
「非同期リセット付きポジティブ・エッジ・トリガ型D-FF」の回路は,先に紹介した「ポジティブ・エッジ・トリガ型D-FF」とほとんど同じです.変更点はリセット信号の追加だけなので,ここではリセット入力“$\overline{RST}$”の影響だけを考えることにします.
リセット入力を“$\overline{RST} = 1$”とした場合
リセット入力を“$\overline{RST} = 1$”とした場合,“$\overline{RST}$”信号が入力される“U2”,“U4”,“U6”は「2入力NANDゲート」として振る舞います.これは図4の回路と等価なので,先に考えた(リセット機能なしの)「ポジティブ・エッジ・トリガ型D-FF」として動作します.
リセット入力を“$\overline{RST} = 0$”とした場合
リセット入力を“$\overline{RST} = 0$”とした状態を図8に示します.“U2”の出力すなわちノード“$X$”は他の信号線のレベルによらず“1”になります.また,“U6”の出力は他の信号線のレベルによらず“1”になります.これに伴い,“U5”の出力は“0”になります.以上のことから,“$\overline{RST} = 0$”を入力することによって任意のタイミングで“$Q = 0$”となることがわかります.
非同期リセット付きD-FFをFPGA上に実装する
ソース・コード
今後のCPU回路設計でよく使う「非同期リセット付きポジティブ・エッジ・トリガ型D-FF」をFPGA上に実装する方法について確認しておきます.リスト1に,Verilog HDLの記述例を示します.
|
---|
リスト1 「非同期リセット付きポジティブ・エッジ・トリガ型D-FF」のVerilog記述例 |
ファイル名は"D_FF.v"とする |
なお,リスト1のVerilogコードによってFPGA内に生成される回路は,先に「デジタル回路ブロック」で作った回路とは厳密には異なります.あくまで「外側から見た挙動が同じ」というだけです.ご注意ください.
ソース・コードの解説
リスト1のVerilogコードの内容を簡単に解説します.
モジュール名は“D_FF”としています.クロック入力端子は“clk”,リセット入力端子は“n_rst”,入力“$D$”の端子は“d”,出力“$Q$”の端子は“q”です.なお,反転出力“$\overline{Q}$”は今後あまり使わないので定義しませんでした.
“always@( )”ブロックを使って,レジスタ“q”の動作を定義しています.リセット入力が“n_rst = 0”のときは値をリセットします.それ以外の場合はレジスタ“q”に入力“d”の値を代入します.
「デジタル回路ブロック」でこの回路を作るためには32個のトランジスタをはんだ付けする必要がありましたが,FPGA上に実装する場合は数行のVerilogコードを書くだけで済んでしまいました(FPGAって便利ですね).
D-FFの動作をシミュレーションで確認する
リスト1で定義したD-FFの動作を論理シミュレーションで確認してみましょう.リスト2に,今回使うテスト・ベンチを示します.なお,シミュレーションの手順やテスト・ベンチの作り方については「Vol.3 Lチカ・ロジックのFPGA実装とシミュレーション(mz-1738-da3)」を参照してください.
|
---|
リスト2 D-FFのテスト・ベンチのVerilogファイル"tb_d_ff.v" |
リスト2の内容を簡単に説明します.テスト・ベンチのモジュール名は“tb_d_ff”としています.クロック信号を生成するためにレジスタ“clk”を用意して,0.5ステップ(今回は`timescaleで1μsを指定しているので0.5μsに相当する)ごとに値が反転するようにしています.また,リセット信号を生成するためにレジスタ“n_rst”を用意して,最初の1ステップだけ“0”にしてその後は10ステップの間“1”にしています.さらにその後,非同期リセットの効果を試すために1ステップだけ“n_rst”の値を“0”にしています.
シミュレーション全体のステップ数を管理するために,“cycle_cnt”というレジスタを用意してクロックの回数をカウントしています.今回は適当なシミュレーションの長さとして,“cycle_cnt = 20”となった時点で終了するようにしました.
シミュレーション対象のモジュール“D_FF”は,“d_ff”という名前でインスタンス化しています.後で波形表示を行うときは,この“d_ff”という名前で内部信号にアクセスすることになります.
ModelSim用のスクリプト・ファイル
無償で使用できる論理シミュレータの“ModelSim”(Starter Edition)を使ってシミュレーションを実行します.ModelSimでシミュレーションを行う場合は,一連の設定をまとめたスクリプト・ファイル(拡張子は“.do”)を用意しておくと便利です.今回のシミュレーションのために作成したスクリプト・ファイルをリスト3に示します.なお,ModelSimを使ったシミュレーションの手順やスクリプト・ファイルの書き方については,「Vol.3 Lチカ・ロジックのFPGA実装とシミュレーション(mz-1738-da3)」を参照してください.
|
---|
リスト3 D-FFのシミュレーションに使うModelSimのスクリプト・ファイル"tb_d_ff.do" |
リスト3のスクリプト・ファイルは,他のシミュレーションにも流用することができます.このスクリプト・ファイルを使いまわす場合は,テスト・ベンチ名の“tb_d_ff”の部分(“vlog”コマンドおよび“vsim”コマンドのパラメータ)と,読み込むVerilogファイルのパス(“vlog”コマンドのパラメータ),そして“tb_d_ff/d_ff/clk”などの信号線の名前(“add wave”コマンドのパラメータ)を適宜書き換えます.
ModelSimによるシミュレーション結果
リスト2のテスト・ベンチおよびリスト3のスクリプトを利用してModelSim上でシミュレーションを実行すると,図9の信号波形が得られます.クロック信号“clk”が立ち上がるタイミングで入力“d”の値が出力“q”に反映されていることがわかります.また,入力“n_rst”が“0”になるとクロックの状態とは無関係に出力“q”が“0”にリセットされるようすも確認できます.
3.Aレジスタ(A REG)の設計
Aレジスタの入出力仕様
具体的なCPUの回路設計を始めます.まずは最も構成が単純な「Aレジスタ」(A REG)から考えます.
Aレジスタに求められる機能は,4ビット幅のデータを記憶することです.記憶するデータを更新するタイミングは,CPU全体に供給されるクロック信号の立ち上がりエッジとします.また,クロックとは非同期でリセットする機能も付けましょう.
データの読み込みを制御する“$LD$”端子を付ける
後でCPUの回路全体を組み立てるときに,このAレジスタのデータ入力端子は4ビットの「データ・バス」に接続されます.このバスに流れているデータはAレジスタに対するものだけではなく,他の回路ブロックに向けたデータも含まれます.そのため,毎回クロックの立ち上がり・エッジでデータを読み込んでいると,不要なデータまで読み込んでしまう可能性があります.そこで,データの読み込みを制御する「ロード端子」“$LD$”を付けることにします.“$LD = 1$”のときは入力されたデータを読み込んで出力に反映させますが,“$LD = 0$”のときは新しいデータをロードせず現在の出力状態を保持します.
入出力端子の決定と特性表
Aレジスタの入出力端子を図10のように定めます.また,レジスタの動作を表す特性表を表3に示します.外部から見てこのとおりの動作を行う回路を設計することが,今後の目標となります.
Aレジスタの内部ブロック図
表3のAレジスタの特性表を満たすものとして,図11のようなブロック図を考えます.記憶回路の本体は,先ほど考えた「非同期リセット付きポジティブ・エッジ・トリガD-FF」をそのまま使います.今回は4ビット・データを記憶する回路なので,D-FFを4個並べます.
新しいデータを読み込むか直前のデータを保持するかを選択するために,信号選択回路である「マルチプレクサ」(multiplexer,MUX)を使います.MUXの信号選択端子を“$LD$”端子として外部に引き出します.“$LD = 0$”のときはD-FFの入力“D”に出力“$Q$”が接続されるようにします.また,“$LD = 1$”のときは外部からの入力データがD-FFの入力“$D$”に接続されるようにします.
Aレジスタを論理ゲート・レベルで作る
図11のブロック図をもとにして論理ゲート・レベルでAレジスタの回路を作ると,図12のようになります.1ビット分の記憶回路を単純に4つ並べた構成になっています.
写真3は,図12に示したAレジスタの回路を「デジタル回路ブロック」で作ったようすです.これがCPUキット“CPU1738”を構成する基板の1つである「A REG基板」となります.回路図と基板上の論理ゲートの配置がほとんど対応しているので,回路図上の信号の流れを実物で確認することができます.
この回路には37個の論理ゲートを使っています.トランジスタ数は合計178個です.
レジスタをFPGA上に実装する
続いて,FPGA上にCPUを作り込む作業について考えます.今回のCPUでは「Aレジスタ」(“A REG”),「Bレジスタ」(“B REG”),「出力ポート」(“OUT PORT”)がほとんど同じ機能をもちます.そこで,これら3つのブロックに共通して使える回路として“REGISTER”という1つのモジュールを作ることにします.
Verilogソース・コードの解説
モジュール“REGISTER”のVerilog記述例をリスト4に示します.
|
---|
リスト4 CPUの各部で使うレジスタのVerilogソース・コード"REGISTER.v" |
これは先に紹介した「非同期リセット付きポジティブ・エッジ・トリガ型D-FF」のVerilogコードを少しだけ変更したもので,“ld = 1”のときは入力“d”の値をレジスタに保持し,“ld = 0”のときは直前の値をそのまま保持するようにしています.
冒頭の“`default_nettype none”は,自分のミスで定義していないwireを使ってしまった場合にエラーを出すために書いています.例えば,定義していない“hoge”というwireを使って“assign hoge = clk;”などと書いても,通常はエラーになりません.このようなVerilogの言語仕様を積極的に活用する場合もありますが,今回は意図しない動作を防ぐために明示的に定義したwireだけを使うようにします.
なお,外部のライブラリなどを利用する場合は,この設定を残しておくと正常に論理合成できない可能性があります(そのライブラリを作った人が暗黙のwireを使っている場合).そこで,ファイルの末尾に“`default_nettype wire”と書くことで,これ以降に読み込まれるファイルに関しては暗黙のwireを許可するようにしておきます.
レジスタの論理シミュレーションをする
リスト4で記述したモジュール“REGISTER”の動作を確認するために,ModelSimを使って論理シミュレーションを実行してみます.シミュレーションに使うテスト・ベンチをリスト5に,ModelSim用のスクリプト・ファイルをリスト6に示します.今回はレジスタに出入りする信号をすべて表示するようにしました.
|
---|
リスト5 レジスタのシミュレーションのためのテスト・ベンチ"tb_register.v" |
|
---|
リスト6 レジスタのシミュレーションのためのスクリプト・ファイル"tb_register.do" |
シミュレーションを実行すると,図13のような波形が得られます.“ld = 1”のときは入力されたデータを読み込み,“ld = 0”のときは出力を保持し続けることが確認できます.また,非同期リセットも問題なく動作していることがわかります.
4. Bレジスタ(B REG)の設計
Bレジスタの入出力仕様
「Bレジスタ」(“B REG”)は4ビットのデータを保持する回路で,基本的な動きはAレジスタと同じです.ただし,Bレジスタの出力は“ROM”の出力と同じバスを共有するので,出力が競合しないようにする必要があります.今回は部品の数を少なくするために,「トライ・ステート・バッファ」を使って出力部分を電気的に切り離すことにしました.電気的に切り離された状態というのは「高抵抗」な状態ということになりますが,ここでは意味を広げて(交流的な意味で)「ハイ・インピーダンス状態」(High-Z状態)と呼ぶことにします.
Bレジスタの入出力仕様を図14に示します.“$OE$”端子は出力を許可する「アウト・イネーブル」(Out Enable)信号を入力する端子です.また,Bレジスタの特性表を表4に示します.
Bレジスタの内部ブロック図
図15にBレジスタの内部ブロック図を示します.基本的な構成はAレジスタと同じですが,出力部分にトライ・ステート・バッファを追加している点が異なります.
Bレジスタを論理ゲート・レベルで作る
図16に,論理ゲート・レベルで設計したBレジスタの回路図を示します.出力部分にトライ・ステート・バッファが付いている以外はAレジスタの回路図と同じです.
写真4は,図16の回路を「デジタル回路ブロック」で作ったようすです.これがCPUキット“CPU1738”の「B REG基板」となります.論理ゲートの数は41個,使用トランジスタ数は210個です.
BレジスタのFPGA実装
FPGA上にBレジスタを実装するときは,先ほど用意した“REGISTER”モジュールを使います.FPGA内部ではトライ・ステート・バッファは使わず,マルチプレクサ(MUX)を使って信号選択を行うようにします.このマルチプレクサは,レジスタどうしを接続する上位の階層(トップ・モジュール)で定義します.
5.I/O(IN PORT,OUT PORT)の設計
出力ポートの入出力仕様
出力ポート(OUT PORT)は,CPU内部のバスを流れるデータを外部回路に対して出力する回路です.バスに流れるデータを適切なタイミングで保持することで,外部回路に対して安定したデータを提供します.
この回路に求められる機能は4ビットのレジスタなので,その実体はAレジスタとまったく同じものになります.図17に出力ポートの入出力仕様,表5に出力ポートの動作を表す特性表を示します.
出力ポートの内部ブロック図
図18に出力ポートの内部ブロック図を示します.これもAレジスタと同じです.
出力ポートを論理ゲート・レベルで作る
図19に出力ポートを論理ゲート・レベルで構成した回路図を示します.基本的にはAレジスタと同じ回路ですが,外部回路の負荷を模擬したものとして出力ラインにLEDを付けています.
入力ポートの入出力仕様
入力ポートは,外部回路から入力された信号を適切なタイミングで内部バスに通す回路です.図20に入力ポートの入出力仕様,表6に入力ポートの動作を表す真理値表を示します.入力ポートの出力回路は“$OE = 0$”の場合に“High-Z”となり,CPUのバス配線から電気的に切り離されます.“$OE = 1$”とした場合は入力ポートの出力がバス配線に接続され,外部回路から入力されたデータをCPU内部のレジスタに格納できるようになります.
入力ポートの内部ブロック図
図21に入力ポートの内部ブロック図を示します.最低限の機能を実装するならばトライ・ステート・バッファだけで十分ですが,今回はシュミット・トリガを入れて外部から入力された信号を波形整形するようにしています.
入力ポートを論理ゲート・レベルで作る
図22に入力ポートを論理ゲート・レベルで作った回路図を示します.シュミット・トリガの入力部分には抵抗とコンデンサで作ったローパス・フィルタを入れてあります.また,入力信号を確認するためにシュミット・トリガの出力部分にLEDを付けました.
CPUキットのI/O基板
CPUキット“CPU1738”では,出力ポートと入力ポートの回路をまとめて「I/O基板」に実装しています.これを写真5に示します.この回路には49個の論理ゲートが使われており,トランジスタ数は合計258個です.
I/OのFPGA実装
CPU内部に出力ポートを実装するときは,先に用意した“REGISTER”モジュールをそのまま使います.また,入力ポートは入力信号を内部バスに接続する際の信号選択の役割をもちますが,FPGAの内部ではトライ・ステート・バッファではなくマルチプレクサを使って実装します.
なお,FPGAの入力ピンはシュミット・トリガ・モードに設定することができます.設定方法はCPU全体をFPGAに実装する時に説明します.
関連資料
-
CPU組み立てキット(ロボット用パーツ付き) CPU1738の取扱説明書
こちらからダウンロードしてください