SFLで作るPICO-16プロセッサ/第4回
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
|
ログイン
]
開始行:
[[SFLで作るPICO-16プロセッサ]]
*実行ユニットの設計 [#o5eada7d]
レジスタファイルとALUを組み合わせて、レジスタ同士の演算を行う回路を設計しましょう。
**コンテンツ [#p043c869]
#contents
*仕様 [#tba8043b]
今回設計する回路は、レジスタファイルとALUを以下のように組み合わせます。~
MUXはマルチプレクサ(複数の入力信号から1つの出力信号を選択する回路)を表しています。~
SFLでは1つの入力ポートや信号線に対して、複数の入力信号がある場合、自動的にマルチプレクサを生成するような機能を持っています。
CENTER:&ref(execution.png);
**入出力 [#rbeeb28c]
-入力
--レジスタファイルへの読み出しレジスタアドレス1(3ビット)
--レジスタファイルへの読み出しレジスタアドレス2(3ビット)
--レジスタファイルへの書き込みレジスタアドレス(3ビット)
--レジスタファイルへの書き込みデータ(16ビット)
-出力
--レジスタファイルのデータ(16ビット)
**サブモジュール [#ebc2afca]
-ALU
--[[第1回>SFLで作るPICO-16プロセッサ/第1回]]で設計したALU。
-レジスタファイル
--[[第2回>SFLで作るPICO-16プロセッサ/第2回]]で設計したレジスタファイル。
**機能 [#fa8c879a]
-ALU演算(AND,OR,XOR,NOT,ADD,SUB,SHL,SHR)
--ALUを使って2つのレジスタの値を演算する。
--regfile[adrs]はレジスタファイルからadrsで示すレジスタを読み出すことを表す。
|機能|意味|動作|
|AND|論理積|regfile[write_adrs] = regfile[read_adrs1] & regfile[read_adrs2]|
|OR|論理和|regfile[write_adrs] = regfile[read_adrs1] | regfile[read_adrs2]|
|XOR|排他的論理和|regfile[write_adrs] = regfile[read_adrs1] @ regfile[read_adrs2]|
|NOT|論理否定|regfile[write_adrs] = ^regfile[read_adrs2]|
|ADD|加算|regfile[write_adrs] = regfile[read_adrs1] + regfile[read_adrs2]|
|SUB|減算|regfile[write_adrs] = regfile[read_adrs1] + regfile[read_adrs2]|
|SHL|左シフト(SHift Left)|regfile[write_adrs] = regfile[read_adrs2] << 1|
|SHR|右シフト(SHift Right)|regfile[write_adrs] = regfile[read_adrs2] >> 1|
-レジスタファイルへの書き込み
--シミュレーション時のレジスタファイルの汎用レジスタを初期化するための機能。
--任意のレジスタへ16ビットのデータを書き込む。
-レジスタファイルへの読み出し
--シミュレーション時のレジスタファイルの汎用レジスタの読み出しを行う機能。
--任意のレジスタの16ビットのデータを読み出す。
*実装 [#ia9bf6c0]
ファイル名はexecution.sfl及び、execution.hとします。
**ヘッダ [#d3102518]
declare execution {
input read_adrs1<3>;
input read_adrs2<3>;
input write_adrs<3>;
input in<16>;
output out<16>;
instrin AND;
instrin OR;
instrin XOR;
instrin NOT;
instrin ADD;
instrin SUB;
instrin SHL;
instrin SHR;
instrin write_regfile;
instrin read_regfile;
instr_arg AND(write_adrs, read_adrs1, read_adrs2);
instr_arg OR(write_adrs, read_adrs1, read_adrs2);
instr_arg XOR(write_adrs, read_adrs1, read_adrs2);
instr_arg NOT(write_adrs, read_adrs2);
instr_arg ADD(write_adrs, read_adrs1, read_adrs2);
instr_arg SUB(write_adrs, read_adrs1, read_adrs2);
instr_arg SHL(write_adrs, read_adrs2);
instr_arg SHR(write_adrs, read_adrs2);
instr_arg write_regfile(write_adrs, in);
instr_arg read_regfile(read_adrs1);
}
**モジュール [#nd41feee]
今回も詳細なスケルトンは用意しません。以下の手順に従って記述してください。
/* 1. サブモジュールとして使用するモジュールのヘッダをインクルード */
module execution {
/* 2. 入出力信号、制御入出力信号を記述 */
/* 3. サブモジュール宣言 */
/* 4. 制御入力に対する動作を記述 */
}
*論理シミュレーション [#dc6e3ffa]
回路が正しく動作しているかどうかを確認します。
+&ref(ex04.tar.gz);をダウンロードする。~
~
解凍方法)
% tar zxvf ex04.tar.gz
+execution.h、execution.sflを作成し、ex04ディレクトリに入れる。
--execution.hは、[[ここ>#d3102518]]から内容をコピーする。
--execution.sflは、[[ここ>#nd41feee]]から内容をコピーして、記述を完成させる。
+[[第1回>SFLで作るPICO-16プロセッサ/第1回]]で作成したalu.h、alu.sflをex04ディレクトリにコピーする。
+[[第2回>SFLで作るPICO-16プロセッサ/第2回]]で作成したregfile.h、regfile.sflをex04ディレクトリにコピーする。
+シミュレーションを行う。
--シミュレーションには次のコマンドを使います。
% make
もしくは、
% make sim
--シミュレーションの結果はsim.logに出力されます。正しく動作していると&ref(sim.log.txt);のようになります。
--余裕があればテストベンチを自由に改変してみましょう。
+波形を観察する。
--波形ビューワの起動には次のコマンドを使います。
% make wave
--
+ファイルをクリーンする。
--ファイルのクリーンには次のコマンドを使います。
% make clean
--シミュレーションや波形ビューワで生成されたファイルのサイズが大きいときもあるので、全て終わったら余計なファイルを削除しておきましょう。
**シミュレーション結果 [#a1345985]
正しく動作していれば、次のような結果が出ます。
|ステップ|動作|r0|r1|r2|r3|r4|r5|r6|r7|
|1|WRITE r0 <= 0f0f|0f0f|0000|0000|0000|0000|0000|0000|0000|
|2|WRITE r1 <= f0f0|0f0f|f0f0|0000|0000|0000|0000|0000|0000|
|3|WRITE r2 <= bc31|0f0f|f0f0|bc31|0000|0000|0000|0000|0000|
|4|WRITE r3 <= 3f9a|0f0f|f0f0|bc31|3f9a|0000|0000|0000|0000|
|5|AND r4 <= r0 & r1|0f0f|f0f0|bc31|3f9a|0000|0000|0000|0000|
|6|OR r5 <= r0 | r1|0f0f|f0f0|bc31|3f9a|0000|ffff|0000|0000|
|7|XOR r6 <= r0 @ r1|0f0f|f0f0|bc31|3f9a|0000|ffff|ffff|0000|
|8|NOT r7 <= ^r0|0f0f|f0f0|bc31|3f9a|0000|ffff|ffff|f0f0|
|9|ADD r4 <= r2 + r3|0f0f|f0f0|bc31|3f9a|fbcb|ffff|ffff|f0f0|
|10|SUB r5 <= r2 - r3|0f0f|f0f0|bc31|3f9a|fbcb|7c97|ffff|f0f0|
|11|SHL r6 <= r0 << 1|0f0f|f0f0|bc31|3f9a|fbcb|7c97|1e1e|f0f0|
|12|SHR r7 <= r0 >> 1|0f0f|f0f0|bc31|3f9a|fbcb|7c97|1e1e|0787|
**テストベンチ [#o1659d1e]
今回のテストベンチで使っているtaskは全部で11個あります。
-initialize~
リセット処理を行います。テストベンチの最初に一度だけ使用します。~
~
使用例)
initialize;
-display_regs~
8個のレジスタの状態を表示します。レジスタファイルへの書き込みを伴うタスクの最後で使っています。~
~
使用例)
display_regs;
-write_data~
指定したレジスタ番号に好きなデータを書き込みます。レジスタ番号とデータを渡します。~
~
使用例)
write_data(3, 16'h4444);
write_data(4, 16'h5555);
-execute_and~
3つの引数を指定します。順に、write_adrs、read_adrs1、read_adrs2を指定し~
regfile[write_adrs] <= regfile[read_adrs1] & regfile[read_adrs2]の動作を行います。~
~
使用例)
execute_and(3, 4, 5);
execute_and(1, 7, 7);
-execute_or~
3つの引数を指定します。順に、write_adrs、read_adrs1、read_adrs2を指定し~
regfile[write_adrs] <= regfile[read_adrs1] | regfile[read_adrs2]の動作を行います。~
~
使用例)
execute_or(3, 4, 5);
execute_or(1, 7, 7);
-execute_xor~
3つの引数を指定します。順に、write_adrs、read_adrs1、read_adrs2を指定し~
regfile[write_adrs] <= regfile[read_adrs1] @ regfile[read_adrs2]の動作を行います。~
~
使用例)
execute_xor(3, 4, 5);
execute_xor(1, 7, 7);
-execute_not~
2つの引数を指定します。順に、write_adrs、read_adrs2を指定し~
regfile[write_adrs] <= ^regfile[read_adrs2]の動作を行います。~
~
使用例)
execute_not(3, 5);
execute_not(1, 7);
-execute_add~
3つの引数を指定します。順に、write_adrs、read_adrs1、read_adrs2を指定し~
regfile[write_adrs] <= regfile[read_adrs1] + regfile[read_adrs2]の動作を行います。~
~
使用例)
execute_add(3, 4, 5);
execute_add(1, 7, 7);
-execute_sub~
3つの引数を指定します。順に、write_adrs、read_adrs1、read_adrs2を指定し~
regfile[write_adrs] <= regfile[read_adrs1] - regfile[read_adrs2]の動作を行います。~
~
使用例)
execute_sub(3, 4, 5);
execute_sub(1, 7, 7);
-execute_shl~
2つの引数を指定します。順に、write_adrs、read_adrs2を指定し~
regfile[write_adrs] <= regfile[read_adrs2] << 1の動作を行います。~
~
使用例)
execute_shl(3, 5);
execute_shl(1, 7);
-execute_shr~
2つの引数を指定します。順に、write_adrs、read_adrs2を指定し~
regfile[write_adrs] <= regfile[read_adrs2] >> 1の動作を行います。~
~
使用例)
execute_shr(3, 5);
execute_shr(1, 7);
*ヒントと豆知識 [#s1cd0fa0]
ここでは、今回の演習に関わる豆知識やヒントを紹介します。
**並列動作ブロック [#l22e5668]
複数の動作を列挙する必要がある場合、SFLでは並列動作ブロックであるpar構文を使用します。~
例えば、レジスタファイル(regfile)からadrs1とadrs2を使ってデータを読み出した後に、その2つの値を加算してoutに出力したいときは、以下のように記述できます。
instruct read_add par {
regfile.read1(adrs1);
regfile.read2(adrs2);
out = regfile.out1 + regfile.out2;
}
これは、1行で書くこともできます。
insturct read_add out = regfile.read1(adrs1).out1 + regfile.read2(adrs2).out2;
ただし、複雑な動作を1行にまとめて書こうとすれば、このように長くなってしまうため、あまりおすすめしません。
また、1つの制御入力の動作に対して複数の出力に値を入れる場合にもpar構文を使用します。~
例えば、前の例に加えて、2つの値を減算した値も同時に出力したい場合を考えてみると、以下のように記述できます。
insturct read_add_sub par {
regfile.read1(adrs1);
regfile.read2(adrs2);
out1 = regfile.out1 + regfile.out2;
out2 = regfile.out1 - regfile.out2;
}
このように、2つの処理を同時に行いたいときにはparという構文を使用する必要があります。~
逆に言えば、回路が同時に2つ以上の処理を行うときは、par構文を使用しなければなりません。
通常のプログラム言語ではこのような概念はありませんが、回路設計においては並列に動作するということを十分に考えて設計する必要があります。
終了行:
[[SFLで作るPICO-16プロセッサ]]
*実行ユニットの設計 [#o5eada7d]
レジスタファイルとALUを組み合わせて、レジスタ同士の演算を行う回路を設計しましょう。
**コンテンツ [#p043c869]
#contents
*仕様 [#tba8043b]
今回設計する回路は、レジスタファイルとALUを以下のように組み合わせます。~
MUXはマルチプレクサ(複数の入力信号から1つの出力信号を選択する回路)を表しています。~
SFLでは1つの入力ポートや信号線に対して、複数の入力信号がある場合、自動的にマルチプレクサを生成するような機能を持っています。
CENTER:&ref(execution.png);
**入出力 [#rbeeb28c]
-入力
--レジスタファイルへの読み出しレジスタアドレス1(3ビット)
--レジスタファイルへの読み出しレジスタアドレス2(3ビット)
--レジスタファイルへの書き込みレジスタアドレス(3ビット)
--レジスタファイルへの書き込みデータ(16ビット)
-出力
--レジスタファイルのデータ(16ビット)
**サブモジュール [#ebc2afca]
-ALU
--[[第1回>SFLで作るPICO-16プロセッサ/第1回]]で設計したALU。
-レジスタファイル
--[[第2回>SFLで作るPICO-16プロセッサ/第2回]]で設計したレジスタファイル。
**機能 [#fa8c879a]
-ALU演算(AND,OR,XOR,NOT,ADD,SUB,SHL,SHR)
--ALUを使って2つのレジスタの値を演算する。
--regfile[adrs]はレジスタファイルからadrsで示すレジスタを読み出すことを表す。
|機能|意味|動作|
|AND|論理積|regfile[write_adrs] = regfile[read_adrs1] & regfile[read_adrs2]|
|OR|論理和|regfile[write_adrs] = regfile[read_adrs1] | regfile[read_adrs2]|
|XOR|排他的論理和|regfile[write_adrs] = regfile[read_adrs1] @ regfile[read_adrs2]|
|NOT|論理否定|regfile[write_adrs] = ^regfile[read_adrs2]|
|ADD|加算|regfile[write_adrs] = regfile[read_adrs1] + regfile[read_adrs2]|
|SUB|減算|regfile[write_adrs] = regfile[read_adrs1] + regfile[read_adrs2]|
|SHL|左シフト(SHift Left)|regfile[write_adrs] = regfile[read_adrs2] << 1|
|SHR|右シフト(SHift Right)|regfile[write_adrs] = regfile[read_adrs2] >> 1|
-レジスタファイルへの書き込み
--シミュレーション時のレジスタファイルの汎用レジスタを初期化するための機能。
--任意のレジスタへ16ビットのデータを書き込む。
-レジスタファイルへの読み出し
--シミュレーション時のレジスタファイルの汎用レジスタの読み出しを行う機能。
--任意のレジスタの16ビットのデータを読み出す。
*実装 [#ia9bf6c0]
ファイル名はexecution.sfl及び、execution.hとします。
**ヘッダ [#d3102518]
declare execution {
input read_adrs1<3>;
input read_adrs2<3>;
input write_adrs<3>;
input in<16>;
output out<16>;
instrin AND;
instrin OR;
instrin XOR;
instrin NOT;
instrin ADD;
instrin SUB;
instrin SHL;
instrin SHR;
instrin write_regfile;
instrin read_regfile;
instr_arg AND(write_adrs, read_adrs1, read_adrs2);
instr_arg OR(write_adrs, read_adrs1, read_adrs2);
instr_arg XOR(write_adrs, read_adrs1, read_adrs2);
instr_arg NOT(write_adrs, read_adrs2);
instr_arg ADD(write_adrs, read_adrs1, read_adrs2);
instr_arg SUB(write_adrs, read_adrs1, read_adrs2);
instr_arg SHL(write_adrs, read_adrs2);
instr_arg SHR(write_adrs, read_adrs2);
instr_arg write_regfile(write_adrs, in);
instr_arg read_regfile(read_adrs1);
}
**モジュール [#nd41feee]
今回も詳細なスケルトンは用意しません。以下の手順に従って記述してください。
/* 1. サブモジュールとして使用するモジュールのヘッダをインクルード */
module execution {
/* 2. 入出力信号、制御入出力信号を記述 */
/* 3. サブモジュール宣言 */
/* 4. 制御入力に対する動作を記述 */
}
*論理シミュレーション [#dc6e3ffa]
回路が正しく動作しているかどうかを確認します。
+&ref(ex04.tar.gz);をダウンロードする。~
~
解凍方法)
% tar zxvf ex04.tar.gz
+execution.h、execution.sflを作成し、ex04ディレクトリに入れる。
--execution.hは、[[ここ>#d3102518]]から内容をコピーする。
--execution.sflは、[[ここ>#nd41feee]]から内容をコピーして、記述を完成させる。
+[[第1回>SFLで作るPICO-16プロセッサ/第1回]]で作成したalu.h、alu.sflをex04ディレクトリにコピーする。
+[[第2回>SFLで作るPICO-16プロセッサ/第2回]]で作成したregfile.h、regfile.sflをex04ディレクトリにコピーする。
+シミュレーションを行う。
--シミュレーションには次のコマンドを使います。
% make
もしくは、
% make sim
--シミュレーションの結果はsim.logに出力されます。正しく動作していると&ref(sim.log.txt);のようになります。
--余裕があればテストベンチを自由に改変してみましょう。
+波形を観察する。
--波形ビューワの起動には次のコマンドを使います。
% make wave
--
+ファイルをクリーンする。
--ファイルのクリーンには次のコマンドを使います。
% make clean
--シミュレーションや波形ビューワで生成されたファイルのサイズが大きいときもあるので、全て終わったら余計なファイルを削除しておきましょう。
**シミュレーション結果 [#a1345985]
正しく動作していれば、次のような結果が出ます。
|ステップ|動作|r0|r1|r2|r3|r4|r5|r6|r7|
|1|WRITE r0 <= 0f0f|0f0f|0000|0000|0000|0000|0000|0000|0000|
|2|WRITE r1 <= f0f0|0f0f|f0f0|0000|0000|0000|0000|0000|0000|
|3|WRITE r2 <= bc31|0f0f|f0f0|bc31|0000|0000|0000|0000|0000|
|4|WRITE r3 <= 3f9a|0f0f|f0f0|bc31|3f9a|0000|0000|0000|0000|
|5|AND r4 <= r0 & r1|0f0f|f0f0|bc31|3f9a|0000|0000|0000|0000|
|6|OR r5 <= r0 | r1|0f0f|f0f0|bc31|3f9a|0000|ffff|0000|0000|
|7|XOR r6 <= r0 @ r1|0f0f|f0f0|bc31|3f9a|0000|ffff|ffff|0000|
|8|NOT r7 <= ^r0|0f0f|f0f0|bc31|3f9a|0000|ffff|ffff|f0f0|
|9|ADD r4 <= r2 + r3|0f0f|f0f0|bc31|3f9a|fbcb|ffff|ffff|f0f0|
|10|SUB r5 <= r2 - r3|0f0f|f0f0|bc31|3f9a|fbcb|7c97|ffff|f0f0|
|11|SHL r6 <= r0 << 1|0f0f|f0f0|bc31|3f9a|fbcb|7c97|1e1e|f0f0|
|12|SHR r7 <= r0 >> 1|0f0f|f0f0|bc31|3f9a|fbcb|7c97|1e1e|0787|
**テストベンチ [#o1659d1e]
今回のテストベンチで使っているtaskは全部で11個あります。
-initialize~
リセット処理を行います。テストベンチの最初に一度だけ使用します。~
~
使用例)
initialize;
-display_regs~
8個のレジスタの状態を表示します。レジスタファイルへの書き込みを伴うタスクの最後で使っています。~
~
使用例)
display_regs;
-write_data~
指定したレジスタ番号に好きなデータを書き込みます。レジスタ番号とデータを渡します。~
~
使用例)
write_data(3, 16'h4444);
write_data(4, 16'h5555);
-execute_and~
3つの引数を指定します。順に、write_adrs、read_adrs1、read_adrs2を指定し~
regfile[write_adrs] <= regfile[read_adrs1] & regfile[read_adrs2]の動作を行います。~
~
使用例)
execute_and(3, 4, 5);
execute_and(1, 7, 7);
-execute_or~
3つの引数を指定します。順に、write_adrs、read_adrs1、read_adrs2を指定し~
regfile[write_adrs] <= regfile[read_adrs1] | regfile[read_adrs2]の動作を行います。~
~
使用例)
execute_or(3, 4, 5);
execute_or(1, 7, 7);
-execute_xor~
3つの引数を指定します。順に、write_adrs、read_adrs1、read_adrs2を指定し~
regfile[write_adrs] <= regfile[read_adrs1] @ regfile[read_adrs2]の動作を行います。~
~
使用例)
execute_xor(3, 4, 5);
execute_xor(1, 7, 7);
-execute_not~
2つの引数を指定します。順に、write_adrs、read_adrs2を指定し~
regfile[write_adrs] <= ^regfile[read_adrs2]の動作を行います。~
~
使用例)
execute_not(3, 5);
execute_not(1, 7);
-execute_add~
3つの引数を指定します。順に、write_adrs、read_adrs1、read_adrs2を指定し~
regfile[write_adrs] <= regfile[read_adrs1] + regfile[read_adrs2]の動作を行います。~
~
使用例)
execute_add(3, 4, 5);
execute_add(1, 7, 7);
-execute_sub~
3つの引数を指定します。順に、write_adrs、read_adrs1、read_adrs2を指定し~
regfile[write_adrs] <= regfile[read_adrs1] - regfile[read_adrs2]の動作を行います。~
~
使用例)
execute_sub(3, 4, 5);
execute_sub(1, 7, 7);
-execute_shl~
2つの引数を指定します。順に、write_adrs、read_adrs2を指定し~
regfile[write_adrs] <= regfile[read_adrs2] << 1の動作を行います。~
~
使用例)
execute_shl(3, 5);
execute_shl(1, 7);
-execute_shr~
2つの引数を指定します。順に、write_adrs、read_adrs2を指定し~
regfile[write_adrs] <= regfile[read_adrs2] >> 1の動作を行います。~
~
使用例)
execute_shr(3, 5);
execute_shr(1, 7);
*ヒントと豆知識 [#s1cd0fa0]
ここでは、今回の演習に関わる豆知識やヒントを紹介します。
**並列動作ブロック [#l22e5668]
複数の動作を列挙する必要がある場合、SFLでは並列動作ブロックであるpar構文を使用します。~
例えば、レジスタファイル(regfile)からadrs1とadrs2を使ってデータを読み出した後に、その2つの値を加算してoutに出力したいときは、以下のように記述できます。
instruct read_add par {
regfile.read1(adrs1);
regfile.read2(adrs2);
out = regfile.out1 + regfile.out2;
}
これは、1行で書くこともできます。
insturct read_add out = regfile.read1(adrs1).out1 + regfile.read2(adrs2).out2;
ただし、複雑な動作を1行にまとめて書こうとすれば、このように長くなってしまうため、あまりおすすめしません。
また、1つの制御入力の動作に対して複数の出力に値を入れる場合にもpar構文を使用します。~
例えば、前の例に加えて、2つの値を減算した値も同時に出力したい場合を考えてみると、以下のように記述できます。
insturct read_add_sub par {
regfile.read1(adrs1);
regfile.read2(adrs2);
out1 = regfile.out1 + regfile.out2;
out2 = regfile.out1 - regfile.out2;
}
このように、2つの処理を同時に行いたいときにはparという構文を使用する必要があります。~
逆に言えば、回路が同時に2つ以上の処理を行うときは、par構文を使用しなければなりません。
通常のプログラム言語ではこのような概念はありませんが、回路設計においては並列に動作するということを十分に考えて設計する必要があります。
ページ名: