命令メモリ
命令メモリ
ここまで、データメモリは接続できましたが、4 + 3 - 2 + 1
を実現するための処理内容はテストベンチから入力していました。
処理内容を保持するための命令メモリというものを導入して、接続します。
命令メモリの構造自体は、データメモリと同様ですが、役割が異なるため、内容が異なります。
まずは、どういうものを命令としてメモリに保持すると良いかを考えてみます。
処理と命令
テストベンチに書かれた処理内容は、以下の 3 つの信号線への入力でした。
それぞれを下の表にまとめます。
テストベンチの data address は、test_address <= test_address + ADDR_W'h01;
という形でインクリメントしていましたが、ここでは実際の値に直しています。
処理 | selector | write enable | data address |
---|---|---|---|
1 | OP_THROUGH_B |
DISABLE |
ADDR_W'h00 |
2 | OP_ADD |
DISABLE |
ADDR_W'h01 |
3 | OP_SUB |
DISABLE |
ADDR_W'h02 |
4 | OP_ADD |
DISABLE |
ADDR_W'h03 |
5 | OP_THROUGH_A |
ENABLE |
ADDR_W'h04 |
この表のように、演算と対象となるデータをひとまとめにしたものを命令と呼び、それぞれを以下のように区別します。
- 演算を表す部分:オペコード(op-code)。ここでは、selector と write enable
- 対象となるデータを表す部分:オペランド(operand)。ここでは、data address
上記の表をより人間にわかりやすい形にしてみましょう。
具体的には、selector や write enable の値を直接指定するより、実際にやりたいことをベースに処理に名前を付けてみます。
たとえば、処理 1 で実際にやりたいことは、メモリからのデータのロードでしたので、LD
という名前にしてみます。
- 処理 1 を
LD
と命名
selector | write enable | わかりやすい表記(ニーモニック) |
---|---|---|
OP_THROUGH_B |
DISABLE |
LD |
このように、処理に人間がわかりやすいような名前を付けたものをニーモニックと呼びます。 また、ニーモニックとデータを合わせたものをアセンブラ表記、処理の実際のビットコードを並べたものを機械語と呼びます。
- テストベンチの処理 1 のアセンブラ表記
命令(ニーモニック) | データ |
---|---|
LD |
0 |
- 機械語(13bit)
selector | write enable | data address |
---|---|---|
0010 |
0 |
0000 0000 |
もっとも単純な実装では、機械語をメモリに入れて、順に呼び出すことで処理を実現できます。これで命令メモリの設計は完了です。
プログラムカウンタ
命令メモリの中のどの処理の実行するかを指定するために、プログラムカウンタを使います。 つまり、プログラムカウンタの値に従って、命令メモリから指定されたアドレスの命令が読み出されます。 今回は単に値を1つずつインクリメントするだけなので、命令メモリに格納された命令を上から順に実行していきます。
4 + 3 - 2 + 1
をアセンブラ表記と機械語で表記したものを以下の表にまとめます。
処理 | アセンブラ表記 | 機械語 |
---|---|---|
1 | LD 0 |
0010 0 0000 0000 |
2 | ADD 1 |
0111 0 0000 0001 |
3 | SUB 2 |
1000 0 0000 0010 |
4 | ADD 3 |
0111 0 0000 0011 |
5 | ST 4 |
1001 1 0000 0100 |
この表の内容を、そのままメモリの内容として保持すれば、命令メモリの完成です。
構成概要
ここまで述べた命令メモリの構成を見ていきます。
命令メモリ自体は単なるメモリなので組み合わせ回路ですが、プログラムカウンタはクロックに同期してインクリメントしなければなりません。 命令メモリの読み出し部分は垂れ流しだといろいろ大変なので、レジスタを取り付けます。 また、`` また、命令メモリに書き込みは行いませんので、データメモリとは異なり、write enable や input 信号は省きます。
次に、命令メモリには selector や write enable の値がごちゃまぜに入っていますので、それぞれを分解する必要があります。 具体的には、メモリから取ってきたデータを以下のように分解し、それぞれを各信号線に接続します。
- 12~9bit 目 → selector
- 8bit 目 → write enable
- 7~0bit 目 → data address
全体としては、以下の図のような回路になります。
なお、命令メモリの中身は、データメモリと同様にファイル imem.dat
に書き出して利用します。
実装
以下に、命令メモリの実装を示します。全体の挙動は、次の通りです。
- クロックに同期して、プログラムカウンタがインクリメントされている
- プログラムカウンタの値に従った、imem のアドレスが常に出力されている
命令メモリのモジュール(simple_instruction_memory.v
)は、前述のデータメモリよりも簡単な構造になっています。
両者の異なる部分は以下のとおりです。
- write enable や input 信号がなくなっている
simple_instruction_memory.v
命令メモリのモジュール(未完成)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
回路合成結果
図:simple_instruction_memory.v
のDigitalJS Onlineによる合成結果[^1]。右下の命令メモリimem
のアドレス(addr
)にプログラムカウンタのレジスタが接続されている。また、プログラムカウンタは常に+1する回路となっている。結果はこちら
テストベンチは以下のようになります。今回は、テストベンチ側でメモリから取ってきたデータを分解しています。
具体的には、17 行目で{}
構文を使って命令メモリモジュールの出力をそれぞれの信号線に分解して受け取っています。
initial begin
の内部はこれまでのものよりもシンプルな形になっています。
#STEP
を使って時間を進めているだけです。
これは、検証するモジュールに入力信号がなく、クロックのみに従って動くためです。
また、毎クロック変化する出力を確認したいので、$monitor
文で信号の変化を観測します。
test_simple_instruction_memory.v
命令メモリのモジュールのテストベンチ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
|
imem.dat
命令メモリの中身
1 2 3 4 5 |
|
ビルド&実行コマンド
iverilog ./testbench_simple_instruction_memory.v ./simple_instruction_memory.v
vvp ./a.out
テストベンチの出力結果
$ iverilog ./testbench_simple_instruction_memory.v ./simple_instruction_memory.v && vvp ./a.out
WARNING: ./simple_instruction_memory.v:14: $readmemb(imem.dat): Not enough words in the file for the requested range [0:7].
VCD info: dumpfile testbench_simple_instruction_memory.vcd opened for output.
---DUMP MEM---
0, 0010000000000
1, 0111000000001
2, 1000000000010
3, 0111000000011
4, 1001100000100
5, xxxxxxxxxxxxx
6, xxxxxxxxxxxxx
7, xxxxxxxxxxxxx
--------------
0010, 0, 00000000
0111, 0, 00000001
1000, 0, 00000010
0111, 0, 00000011
1001, 1, 00000100
./testbench_simple_instruction_memory.v:55: $finish called at 60000 (1ps)
テストベンチの波形
演習
- 以下の動作をするような命令列を作り、命令メモリ
imem.dat
を作成してみる。なお、dmem
はデータメモリのこと。dmem
のアドレス0x0番地を読むdmem
のアドレス0x2番地の値を掛け算するdmem
のアドレス0x4番地の値を引き算するdmem
のアドレス0x6番地の値を足し算するdmem
のアドレス0x8番地に値を格納する
dmem
の内容が以下の形であったときの、上記の命令列の計算結果は何か?7 6 5 4 3 2 1
命令メモリの接続
作成した命令メモリを、ALU_DMEM 回路に接続します。 これで、ここまでに個別に作成した以下の構成要素を接続した簡単なアキュムレータ・マシンが完成です。
- ALU:
alu4.v
- データパス回路:
simple_datapath.v
- データメモリ:
simple_memory.v
- 命令メモリ:
simple_instruction_memory.v
構成概要
全体の構成図は以下のようになり、接続関係は以下の点がポイントです。 こうすることで、命令メモリからの出力に従って、ALU で指定の演算を行ったり、データメモリを読み書きできます。
- 命令メモリの出力を、ALU_DMEM 回路の 3 つの入力に接続
動作としては、クロックに従って命令メモリが読み出され、それに従って ALU やデータメモリが動く、という形です。 なお、回路全体には、クロック信号以外の外部入力が存在ない自律的な回路です。
実装
構成概要に従って、命令メモリと ALU_DMEM 回路に接続します。
以下のテストベンチで、構成概要と同じになるようにそれぞれを接続しますが、一部未完成です。
演習として、未完成の部分を完成させてみましょう。
なお、命令メモリには、これまでと同様に4 + 3 - 2 + 1
をやる命令が入っています。
testbench_accumulator.v
アキュムレータのモジュールのテストベンチ(未完成)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
|
ビルド&実行コマンド
iverilog alu4.v simple_datapath.v simple_instruction_memory.v simple_memory.v testbench_accumulator.v
vvp ./a.out
演習
- 構成概要に従って、テストベンチを完成させる
- 計算の途中と結果が正しいかを、テストベンチで確認
22 + 7 * 6 = 64
を実現してみる- データメモリには22,7,6の順で値が入っているものとする
学年 + 組 * 番号
をやってみる- データメモリには学年,組,番号の順で値が入っているものとする
ここまでで、簡単なアキュムレータ・マシンは完成です。 全体の動作としては、まず、プログラムカウンタに従って上から順番に命令メモリを読み出します。 次に、命令メモリに書かれた処理とデータメモリのアドレスを読み出し、計算を実行しています。