SN/Xの解読
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
|
ログイン
]
開始行:
[[SN/X]]
*SN/Xの解読 [#nb1c80f3]
パルテノン研究会が主催している第11-14回ASICデザインコンテストの規定課題である16ビットCPUの例題パッケージであるSN/Xの解説。
----
#contents
**SN/Xの構成 [#ta7c0e2a]
CENTER:&ref(snx.png);
**SN/Xの命令 [#pbf8c2e4]
***命令形式 [#m034c8f2]
SN/Xは16ビットからなるR形式とI形式の命令をサポートしています。
||>|>|>|>|CENTER:フィールド|
|命令形式|15-12|11-10|9-8|7-6|5-0|
|R|OP|R2|R3|R1||
|I|OP|R1|R2|>|I|
***命令セット [#o0109db8]
SN/Xがサポートしている命令は以下の通りです。
|命令|形式|例|意味|
|ADD|R|add $1, $2, $3|$1 = $2 + $3|
|AND|R|and $1, $2, $3|$1 = $2 & $3|
|SLT|R|slt $1, $2, $3|if($s2<$s3) $s1=1 ; else $s1=0|
|NOT|R|not $1, $2|$1 = ^$2|
|SR|R|sr $1, $2|$1 = $2 >> 1|
|HLT||hlt|CPUを停止する|
|LD|I|ld $1, 10($2)|$s1 = memory[$s2+10]|
|ST|I|st $1, 10($2)|memory[$s2+10] = $1|
|LDA|I|lda $1, 10($2)|$1 = $2 + 10|
|BZ|I|bz $1, L001|if($1==0) goto L001|
|BAL|I|bal $1, L001|$1 = PC+1 ; goto L001|
**ソースと解説 [#v20f8930]
配布されているパッケージに入っているsnx.sflにSN/Xの記述が書いてあります。~
ただし、そのままでは読みにくい部分もあるので、読みやすいように構文を整えた方が良いでしょう。~
~
以下ではSN/Xを各部位に分けて説明していきます。
***Define部 [#xbaead6b]
%d ADD 0x0
%d AND 0x1
%d SLT 0x3
%d NOT 0x4
%d SR 0x6
%d HLT 0x7
%d LD 0x8
%d ST 0x9
%d LDA 0xa
%d BZ 0xe
%d BAL 0xf
ここでは、SN/Xがサポートする命令を、4ビットのオペコードに割り当てていきます。~
上記の表と対応付けて見てみると、4ビット目が1のとき、I形式の命令になっていることが分かります。
%d ITYPE op<15>
%d OPCODE op<15:12>
%d R2 op<11:10>
%d R3 op<9:8>
%d R1 op<7:6>
%d IRSEL op<1:0>
%d I 16#op<7:0>
%d dR1 opreg<7:6>
%d dR2 opreg<11:10>
%d dR3 opreg<9:8>
%d dI 16#opreg<7:0>
ここでは、命令コードがどのような構成をしているかを示していると共に、~
各部位をdefineしておくことによって、回路を記述していく上で読み出しやすいようにしてあります。
***snxモジュールのdeclare文 [#f21062b8]
declare snx {
input inst<16>;
input datai<16>;
output datao<16>;
output iadrs<16>;
output adrs<16>;
instrout inst_read;
instrout inst_write;
instrout memory_read;
instrout memory_write;
instrout wb;
instrout hlt;
}
CPUのコアとなるsnxモジュールの入出力が定義されています。~
declare文は、他のモジュールでこのモジュールを使用する際に必要となります。~
以下のdeclare文も同様です。
***inc16モジュールのdeclare文 [#jff11545]
declare inc16 {
input in<16>;
output out<16>;
instrin do;
instr_arg do(in);
}
16ビットのPCをインクリメントするinc16モジュールの入出力が定義されています。
***cla16モジュールのdeclare文 [#ne4d6acb]
declare cla16 {
input cin, in1<16>, in2<16>;
output out<16>;
instrin do;
instr_arg do(cin, in1, in2);
}
主な演算をする16ビットの加算器であるcla16モジュールの入出力が定義されています。~
ここではCarry lookahead adderを仕様しています。
***reg4モジュールのdeclare文 [#u8a29091]
declare reg4 {
input in<16>, inadr<2>, outaadr<2>, outbadr<2>;
output outa<16>, outb<16>;
instrin reada, readb, write;
instr_arg reada(outaadr);
instr_arg readb(outbadr);
instr_arg write(inadr, in);
}
内部に16ビットのレジスタを4つ持つレジスタファイルであるreg4モジュールの入出力が定義されています。~
読み出しポートを2つ、書き込みポートを1つ持っています。
***snxctlモジュールのdeclare文 [#o6314577]
declare snxctl {
instrout setop, npc2pc, alu2pc, wb, npc,
hlt, cla2mar, r2grnum, opr1set,I2clain1,
opr12clain1, opr2zero, opr2reg, aluadd, aluslt,
aluand, alunot, alusr, rwb, ldawb, balwb,
store,load;
input op<16>;
input opr1zero;
instrin start;
}
snxの動作を制御するsnxctlモジュールの入出力が定義されています。~
多数のinstroutが定義されていますが、一つの制御端子に一つの動作が対応するようになります。
***snxctlモジュール [#r3360fc1]
snxの動作を制御するsnxctlモジュールです。ここからは細部に渡って説明を加えていきます。
module snxctl {
instrout setop, npc2pc, alu2pc, wb, npc,
hlt, cla2mar, r2grnum, opr1set, I2clain1,
opr12clain1, opr2zero, opr2reg, aluadd, aluslt,
aluand, alunot, alusr, rwb, ldawb, balwb,
store, load;
input op<16>;
input opr1zero;
instrin start;
入出力を定義しています。
sel br_taken;
instrself dADD, dAND, dSLT, dNOT, dSR, dHLT, dLD, dST, dLDA, dBZ, dBAL;
instrself decode;
内部端子と内部制御端子宣言しています。
stage_name ifetch { task ift(); }
stage_name exec { task exe(); }
stage_name memex {
task tstore();
task tload();
}
SN/Xの動作はifetch, exec, memexの3つのステージで構成されています。~
ここにはそれぞれのステージの名前とタスクの宣言をしています。~
~
タスクはステージを起動するための制御端子のようなもので、必要に応じて引数を指定することも可能です。
instruct start generate ifetch.ift();
start信号がアサートされたとき、ifetchステージを起動します。
instruct decode any {
(OPCODE == ADD): dADD();
(OPCODE == AND): dAND();
(OPCODE == SLT): dSLT();
(OPCODE == NOT): dNOT();
(OPCODE == SR ): dSR();
(OPCODE == HLT): dHLT();
(OPCODE == LD ): dLD();
(OPCODE == ST ): dST();
(OPCODE == LDA): dLDA();
(OPCODE == BZ ): dBZ();
(OPCODE == BAL): dBAL();
}
内部制御端子であるdecodeがアサートされたとき、この処理が動きます。~
名前からも分かる通り、OPCODE(op<15:12>)を判定して、その命令コードが何の命令を実行するかを判定します。
stage ifetch {
par {
setop();
relay exec.exe();
}
}
命令コードを命令メモリからとってくるifetchステージについて記述してあります。~
制御端子の出力であるsetopをアサートして、ステージの処理を次のexecステージにリレーしています。~
ここでのrelay文はifetchステージを終了させ、次のexecステージを起動するという意味を示しています。~
~
setopの動作の実体はsnxモジュールに定義されています。~
ここから以下も同様に、制御端子の出力の動作の実体は全てsnxに定義されています。~
~
このように、動作を分けた記述が、SN/Xを解読していく上で最も面倒なところと言えるかもしれません。
stage exec {
par {
decode();
br_taken = dBAL | (dBZ & opr1zero);
npc();
execステージではまず、decode処理を行います。decodeの動作は上記で説明した通りです。~
br_takenは分岐が成立するかどうかを調べて、成立するときは1、そうでなければ0を示します。~
BALは無条件ジャンプ、BZが条件付き(指定したレジスタが0の場合)ジャンプとなっています。~
~
npcは、PCのインクリメントを行います。
any {
br_taken : par {
alu2pc();
wb();
relay ifetch.ift();
}
br_takenによって分岐が成立するとき、alu2pcによってaluの出力でPCを更新します。~
wbは、シミュレーション時にレジスタファイルの中身を表示させるための制御端子なので、ここでは無視して構いません。~
~
最後に、命令の実行が2サイクルで終了したので、ステージの処理を再び最初のifetchステージにリレーします。
dLD : par {
cla2mar();
r2grnum();
npc2pc();
relay memex.tload();
}
LD命令のときは、cla2marによって加算器の出力をメモリの読み込みアドレスとします。~
r2grnumによってR2で指定したレジスタをロードの対象とし、npc2pcでインクリメントしたPCの値でPCを更新します。~
最後に、ステージの処理をmemexステージにリレーします。その際、tloadというタスクで起動しています。~
これはmemexステージにおいて、tload信号がアサートされている状態になることを示します。~
~
メモリから値をロードする動作は次のmemexステージで行います。
dST : par {
cla2mar();
r2grnum();
npc2pc();
relay memex.tstore();
}
ST命令のときは、上記と同様に書き込みアドレスとストアの対象となるレジスタをセットし、PCを更新します。~
最後に、ステージの処理をmemexステージにリレーします。その際、tstoreというタスクで起動しています。~
これはmemexステージにおいて、tstore信号がアサートされている状態になることを示します。~
~
LD命令のtloadと、ST命令のtstoreをこのように区別しておくことで、memexステージにおける処理を判定しています。
dHLT : par {
hlt();
finish;
}
HLT命令のときは、execステージの処理を終了させ、CPUを停止させます。~
hltによってその制御が出力され、シミュレーションを終了させることができます。
else : par {
npc2pc();
wb();
relay ifetch.ift();
}
}
上記の命令に該当しなかった場合は、PCを更新して、ステージの処理をifetchステージにリレーさせます。
opr1set();
opr1setによってR2が指すレジスタのデータをレジスタファイルからopr1に読み出します。~
opr1はsnxモジュールに定義されています。
any {
ITYPE : I2clain1();
else : opr12clain1();
}
I形式の命令かを判断し、その場合、I2clain1によって加算器の一方の入力に即値を使用します。~
そうでない場合は加算器の一方の入力にopr1の値を使用します。
any {
ITYPE & (R3 == 0b00) : opr2zero();
else : opr2reg();
}
I形式の命令で、R3が0である場合opr2zeroによってopr2に0をセットします。~
そうでない場合はR3が指すレジスタのデータをレジスタファイルからopr2に読み出します。
any {
(ITYPE | dADD) : aluadd();
dSLT : aluslt();
dAND : aluand();
dNOT : alunot();
dSR : alusr();
}
この部分がALUとなります。~
I形式かADD命令の場合はaluaddによって加算を、SLT命令のときはalusltによって大小比較を、~
AND命令のときはaluandによって論理積を、NOT命令のときはalunotによって論理否定を、SR命令のときはalusrによって右シフトを行います。
any {
^ITYPE : rwb();
dLDA : ldawb();
dBAL : balwb();
}
}
}
rwb, ldawb, balwbは、レジスタファイルに値を書き込む処理をそれぞれ行います。~
I形式の命令の場合はR1が指すレジスタにALUの出力を、LDA命令の場合はR2が指すレジスタにALUの出力を書き込みます。~
BAL命令の場合はというと、R2が指すレジスタにインクリメントしたPCの値を書き込みます。~
~
これは、命令のアドレスの戻り値となります。~
簡単に説明すると、関数の呼出の際にそのアドレスを記憶しておいて、関数の動作が終了すると、そのアドレスに戻ってくるような動作を実現できます。~
~
以上でexecステージの記述が終了しました。~
上から逐次的に解説していきましたが、各処理が並行に動いていることを忘れないでください。
stage memex {
par {
any {
memex.tstore : store();
memex.tload : load();
}
relay ifetch.ift();
wb();
}
}
}
最後にmemexステージです。~
memexステージを起動する際に、LD命令とST命令でtstore, tloadというタスクの区別をしました。~
ここではそのtstore, tloadを判定して、storeによってメモリへの書き込み、loadによってメモリからの読み込みを行っています。~
~
メモリアクセス終了後、ステージ処理をifetchステージにリレーさせます。~
LD命令とST命令は3サイクルの命令となっています。~
~
以上で、snxctlモジュールの解説は終了になります。
***snxモジュール [#p1619ad5]
CPUのコアとなるsnxモジュールです。~
snxctlで使用されていた制御の動作の実体が定義されています。
module snx {
input inst<16>;
input datai<16>;
output datao<16>;
output iadrs<16>;
output adrs<16>;
instrout inst_read;
instrout inst_write;
instrout memory_read;
instrout memory_write;
instrout wb;
instrout hlt;
入出力を定義しています。
reg_wr pc<16>;
16ビットのPC(プログラムカウンタ)用のレジスタです。
reg_wr st0;
reg st1, st2;
このレジスタはstart信号を一度だけアサートさせるために使用します。
snxctl ctl;
inc16 inc;
cla16 cla;
reg4 gr;
snxに必要なサブモジュールを宣言しています。
reg opreg<16>, mar<16>, regnum<2>;
命令コード用、メモリアドレス用、メモリアクセスの際に操作するレジスタの指定用のレジスタです。
sel opr1<16>, opr2<16>, nxpc<16>;
sel aluo<16>, clain1<16>;
snxで使用する内部端子の定義です。説明は省略します。
instr_arg memory_write(adrs,datao);
instr_arg memory_read(adrs);
instr_arg inst_read(iadrs);
制御出力端子の引数を宣言します。~
これは制御出力端子をアサートする際に渡した引数をoutputに接続するという動作をします。
par {
st0 := 0b1;
st1 := st0;
st2 := st1;
if((st2 == 0b0) & (st1 == 0b1)) ctl.start();
この部分は一度だけ、ctl(snxctl)のstartをアサートする記述です。
ctl.op = opreg;
ctl.opr1zero = (opr1 == 0x0000);
}
ctl(snxctl)の入力opにopregを、opr1zeroに(opr == 0x0000)をそれぞれ入力させています。
instruct ctl.setop opreg := inst_read(pc).inst;
ctl(snxctl)のsetopがアサートされたとき、pcのアドレスの命令コードを命令メモリから読み出して、opregに書き込みます。~
ifetchステージで行われる命令フェッチの処理です。
instruct ctl.npc nxpc = inc.do(pc).out;
npcがアサートされたとき、pcをインクリメントした値をnxpcにセットします。
instruct ctl.npc2pc pc := nxpc;
npc2pcがアサートされたとき、nxpcの値でpcを更新します。
instruct ctl.alu2pc pc := aluo;
alu2pcがアサートされたとき、aluoの値でpcを更新します。~
これは分岐が成立したときに呼び出される処理です。
instruct ctl.wb wb();
wbがアサートされたとき、データが更新されたことを知らせる制御出力端子wbをアサートします。~
今回の設計では無視しても問題ありません。
instruct ctl.hlt hlt();
hltがアサートされたとき、CPUを停止したことを知らせる制御出力端子hltをアサートします。~
シミュレーションを止めるために使用しています。
instruct ctl.cla2mar mar := cla.out;
cla2marがアサートされたとき、claの出力であるcla.outでmarを更新します。
instruct ctl.r2grnum regnum := dR2;
r2grnumがアサートされたとき、dR2(opcode<11:10>)でregnumを更新します。~
これはR2で指定したレジスタの番号をregnumに格納しています
instruct ctl.opr1set opr1 = gr.reada(dR2).outa;
opr1setがアサートされたとき、レジスタファイル(gr)からdR2(opcode<11:10>)で指定されたのレジスタに対応するデータを読み出して、opr1にセットします。
instruct ctl.I2clain1 clain1 = dI;
I2clain1がアサートされたとき、dIをclain1にセットします。
instruct ctl.opr12clain1 clain1 = opr1;
opr12clain1がアサートされたとき、opr1をclain1にセットします。
instruct ctl.opr2zero opr2 = 0x0000;
opr2zeroがアサートされたとき、0をopr2にセットします。
instruct ctl.opr2reg opr2 = gr.readb(dR3).outb;
opr2regがアサートされたとき、レジスタファイル(gr)からdR3(opcode<9:8>)で指定されたのレジスタに対応するデータを読み出して、opr2にセットします。
instruct ctl.aluadd aluo = cla.do(0b0, clain1, opr2).out;
aluaddがアサートされたとき、clain1とopr2を加算した結果をaluoにセットします。
instruct ctl.aluslt par {
cla.do(0b1, clain1, ^opr2);
aluo = 15#0b0 ||
((clain1<15> & ^opr2<15>)|
(cla.out<15> & ^clain1<15> & ^opr2<15>)|
(cla.out<15> & clain1<15> & opr2<15>));
}
alusltがアサートされたとき、clain1とopr2の否定の減算処理を行い、大小判定した結果をaluoにセットします。
instruct ctl.aluand aluo = opr1 & opr2;
aluandがアサートされたとき、opr1とopr2の論理積の結果をaluoにセットします。
instruct ctl.alunot aluo = ^opr1;
alunotがアサートされたとき、opr1の論理否定をaluoにセットします。
instruct ctl.alusr aluo = 0b0 || opr1<15:1> ;
alusrがアサートされたとき、opr1を右シフトした結果をaluoにセットします。
instruct ctl.rwb gr.write(dR1,aluo);
rwbがアサートされたとき、aluoの値をレジスタファイル(gr)のdR1(opreg<7:6>)で指定されたのレジスタに書き込みます。
instruct ctl.ldawb gr.write(dR2, aluo);
ldawbがアサートされたとき、nxpcの値をレジスタファイル(gr)のdR2(opreg<11:10>)で指定されたのレジスタに書き込みます。
instruct ctl.balwb gr.write(dR2, nxpc);
balwbがアサートされたとき、nxpcの値をレジスタファイル(gr)のdR2(opreg<11:10>)で指定されたのレジスタに書き込みます。
instruct ctl.store memory_write(mar,gr.reada(regnum).outa);
storeがアサートされたとき、レジスタファイル(gr)のregnum(dR2(opcode<11:10>))で指定されたのレジスタの値をデータメモリのmar番地に書き込みます。
instruct ctl.load gr.write(regnum, memory_read(mar).datai);
}
loadがアサートされたとき、データメモリのmar番地のデータをレジスタファイル(gr)のregnum(dR2(opcode<11:10>))で指定されたのレジスタに書き込みます。
***reg4モジュール [#g931df3d]
内部に16ビットのレジスタを4つ持つレジスタファイルであるreg4モジュールです。
module reg4 {
input in<16>;
input inadr<2>;
input outaadr<2>;
input outbadr<2>;
output outa<16>;
output outb<16>;
instrin reada;
instrin readb;
instrin write;
入出力を定義しています。
reg r0<16>, r1<16>, r2<16>, r3<16>;
16ビットのレジスタを4つ定義しています。
instruct reada any {
outaadr == 0b00 : outa = r0;
outaadr == 0b01 : outa = r1;
outaadr == 0b10 : outa = r2;
outaadr == 0b11 : outa = r3;
}
制御入力端子readaがアサートされたとき、outaにoutaddrが示すレジスタのデータを出力します。
instruct readb any {
outbadr == 0b00 : outb = r0;
outbadr == 0b01 : outb = r1;
outbadr == 0b10 : outb = r2;
outbadr == 0b11 : outb = r3;
}
制御入力端子readbがアサートされたとき、outbにoutbddrが示すレジスタのデータを出力します。
instruct write any {
inadr == 0b00 : r0 := in;
inadr == 0b01 : r1 := in;
inadr == 0b10 : r2 := in;
inadr == 0b11 : r3 := in;
}
}
制御入力端子writeがアサートされたとき、inadrが示すレジスタにinのデータを書き込みます。~
これによって、読み出しポートを2つ、書き込みポートを1つ持つレジスタファイルを実現しています。
***inc16モジュール [#nb139142]
16ビットのPCをインクリメントするinc16モジュールです。~
circuitは内部で算術演算子を使用するときに使うモジュールの宣言方法です。
circuit inc16 {
input in<16>;
output out<16>;
instrin do;
入出力を定義しています。
instruct do out = in + 0x0001;
}
制御入力端子doがアサートされたとき、inをインクリメントした結果をoutに出力します。
***cla16モジュール [#la661995]
主な演算をする16ビットの加算器であるcla16モジュールです。
circuit cla16 {
input cin;
input in1<16>;
input in2<16>;
output out<16>;
instrin do;
入出力を定義しています。
instruct do out = in1 + in2 + (15#0b0 || cin);
}
制御入力端子doがアサートされたとき、in1, in2, cinを加算した結果をoutに出力します。~
ここでは合成の際にCarry lookahed adderが出力されるようになっています。
終了行:
[[SN/X]]
*SN/Xの解読 [#nb1c80f3]
パルテノン研究会が主催している第11-14回ASICデザインコンテストの規定課題である16ビットCPUの例題パッケージであるSN/Xの解説。
----
#contents
**SN/Xの構成 [#ta7c0e2a]
CENTER:&ref(snx.png);
**SN/Xの命令 [#pbf8c2e4]
***命令形式 [#m034c8f2]
SN/Xは16ビットからなるR形式とI形式の命令をサポートしています。
||>|>|>|>|CENTER:フィールド|
|命令形式|15-12|11-10|9-8|7-6|5-0|
|R|OP|R2|R3|R1||
|I|OP|R1|R2|>|I|
***命令セット [#o0109db8]
SN/Xがサポートしている命令は以下の通りです。
|命令|形式|例|意味|
|ADD|R|add $1, $2, $3|$1 = $2 + $3|
|AND|R|and $1, $2, $3|$1 = $2 & $3|
|SLT|R|slt $1, $2, $3|if($s2<$s3) $s1=1 ; else $s1=0|
|NOT|R|not $1, $2|$1 = ^$2|
|SR|R|sr $1, $2|$1 = $2 >> 1|
|HLT||hlt|CPUを停止する|
|LD|I|ld $1, 10($2)|$s1 = memory[$s2+10]|
|ST|I|st $1, 10($2)|memory[$s2+10] = $1|
|LDA|I|lda $1, 10($2)|$1 = $2 + 10|
|BZ|I|bz $1, L001|if($1==0) goto L001|
|BAL|I|bal $1, L001|$1 = PC+1 ; goto L001|
**ソースと解説 [#v20f8930]
配布されているパッケージに入っているsnx.sflにSN/Xの記述が書いてあります。~
ただし、そのままでは読みにくい部分もあるので、読みやすいように構文を整えた方が良いでしょう。~
~
以下ではSN/Xを各部位に分けて説明していきます。
***Define部 [#xbaead6b]
%d ADD 0x0
%d AND 0x1
%d SLT 0x3
%d NOT 0x4
%d SR 0x6
%d HLT 0x7
%d LD 0x8
%d ST 0x9
%d LDA 0xa
%d BZ 0xe
%d BAL 0xf
ここでは、SN/Xがサポートする命令を、4ビットのオペコードに割り当てていきます。~
上記の表と対応付けて見てみると、4ビット目が1のとき、I形式の命令になっていることが分かります。
%d ITYPE op<15>
%d OPCODE op<15:12>
%d R2 op<11:10>
%d R3 op<9:8>
%d R1 op<7:6>
%d IRSEL op<1:0>
%d I 16#op<7:0>
%d dR1 opreg<7:6>
%d dR2 opreg<11:10>
%d dR3 opreg<9:8>
%d dI 16#opreg<7:0>
ここでは、命令コードがどのような構成をしているかを示していると共に、~
各部位をdefineしておくことによって、回路を記述していく上で読み出しやすいようにしてあります。
***snxモジュールのdeclare文 [#f21062b8]
declare snx {
input inst<16>;
input datai<16>;
output datao<16>;
output iadrs<16>;
output adrs<16>;
instrout inst_read;
instrout inst_write;
instrout memory_read;
instrout memory_write;
instrout wb;
instrout hlt;
}
CPUのコアとなるsnxモジュールの入出力が定義されています。~
declare文は、他のモジュールでこのモジュールを使用する際に必要となります。~
以下のdeclare文も同様です。
***inc16モジュールのdeclare文 [#jff11545]
declare inc16 {
input in<16>;
output out<16>;
instrin do;
instr_arg do(in);
}
16ビットのPCをインクリメントするinc16モジュールの入出力が定義されています。
***cla16モジュールのdeclare文 [#ne4d6acb]
declare cla16 {
input cin, in1<16>, in2<16>;
output out<16>;
instrin do;
instr_arg do(cin, in1, in2);
}
主な演算をする16ビットの加算器であるcla16モジュールの入出力が定義されています。~
ここではCarry lookahead adderを仕様しています。
***reg4モジュールのdeclare文 [#u8a29091]
declare reg4 {
input in<16>, inadr<2>, outaadr<2>, outbadr<2>;
output outa<16>, outb<16>;
instrin reada, readb, write;
instr_arg reada(outaadr);
instr_arg readb(outbadr);
instr_arg write(inadr, in);
}
内部に16ビットのレジスタを4つ持つレジスタファイルであるreg4モジュールの入出力が定義されています。~
読み出しポートを2つ、書き込みポートを1つ持っています。
***snxctlモジュールのdeclare文 [#o6314577]
declare snxctl {
instrout setop, npc2pc, alu2pc, wb, npc,
hlt, cla2mar, r2grnum, opr1set,I2clain1,
opr12clain1, opr2zero, opr2reg, aluadd, aluslt,
aluand, alunot, alusr, rwb, ldawb, balwb,
store,load;
input op<16>;
input opr1zero;
instrin start;
}
snxの動作を制御するsnxctlモジュールの入出力が定義されています。~
多数のinstroutが定義されていますが、一つの制御端子に一つの動作が対応するようになります。
***snxctlモジュール [#r3360fc1]
snxの動作を制御するsnxctlモジュールです。ここからは細部に渡って説明を加えていきます。
module snxctl {
instrout setop, npc2pc, alu2pc, wb, npc,
hlt, cla2mar, r2grnum, opr1set, I2clain1,
opr12clain1, opr2zero, opr2reg, aluadd, aluslt,
aluand, alunot, alusr, rwb, ldawb, balwb,
store, load;
input op<16>;
input opr1zero;
instrin start;
入出力を定義しています。
sel br_taken;
instrself dADD, dAND, dSLT, dNOT, dSR, dHLT, dLD, dST, dLDA, dBZ, dBAL;
instrself decode;
内部端子と内部制御端子宣言しています。
stage_name ifetch { task ift(); }
stage_name exec { task exe(); }
stage_name memex {
task tstore();
task tload();
}
SN/Xの動作はifetch, exec, memexの3つのステージで構成されています。~
ここにはそれぞれのステージの名前とタスクの宣言をしています。~
~
タスクはステージを起動するための制御端子のようなもので、必要に応じて引数を指定することも可能です。
instruct start generate ifetch.ift();
start信号がアサートされたとき、ifetchステージを起動します。
instruct decode any {
(OPCODE == ADD): dADD();
(OPCODE == AND): dAND();
(OPCODE == SLT): dSLT();
(OPCODE == NOT): dNOT();
(OPCODE == SR ): dSR();
(OPCODE == HLT): dHLT();
(OPCODE == LD ): dLD();
(OPCODE == ST ): dST();
(OPCODE == LDA): dLDA();
(OPCODE == BZ ): dBZ();
(OPCODE == BAL): dBAL();
}
内部制御端子であるdecodeがアサートされたとき、この処理が動きます。~
名前からも分かる通り、OPCODE(op<15:12>)を判定して、その命令コードが何の命令を実行するかを判定します。
stage ifetch {
par {
setop();
relay exec.exe();
}
}
命令コードを命令メモリからとってくるifetchステージについて記述してあります。~
制御端子の出力であるsetopをアサートして、ステージの処理を次のexecステージにリレーしています。~
ここでのrelay文はifetchステージを終了させ、次のexecステージを起動するという意味を示しています。~
~
setopの動作の実体はsnxモジュールに定義されています。~
ここから以下も同様に、制御端子の出力の動作の実体は全てsnxに定義されています。~
~
このように、動作を分けた記述が、SN/Xを解読していく上で最も面倒なところと言えるかもしれません。
stage exec {
par {
decode();
br_taken = dBAL | (dBZ & opr1zero);
npc();
execステージではまず、decode処理を行います。decodeの動作は上記で説明した通りです。~
br_takenは分岐が成立するかどうかを調べて、成立するときは1、そうでなければ0を示します。~
BALは無条件ジャンプ、BZが条件付き(指定したレジスタが0の場合)ジャンプとなっています。~
~
npcは、PCのインクリメントを行います。
any {
br_taken : par {
alu2pc();
wb();
relay ifetch.ift();
}
br_takenによって分岐が成立するとき、alu2pcによってaluの出力でPCを更新します。~
wbは、シミュレーション時にレジスタファイルの中身を表示させるための制御端子なので、ここでは無視して構いません。~
~
最後に、命令の実行が2サイクルで終了したので、ステージの処理を再び最初のifetchステージにリレーします。
dLD : par {
cla2mar();
r2grnum();
npc2pc();
relay memex.tload();
}
LD命令のときは、cla2marによって加算器の出力をメモリの読み込みアドレスとします。~
r2grnumによってR2で指定したレジスタをロードの対象とし、npc2pcでインクリメントしたPCの値でPCを更新します。~
最後に、ステージの処理をmemexステージにリレーします。その際、tloadというタスクで起動しています。~
これはmemexステージにおいて、tload信号がアサートされている状態になることを示します。~
~
メモリから値をロードする動作は次のmemexステージで行います。
dST : par {
cla2mar();
r2grnum();
npc2pc();
relay memex.tstore();
}
ST命令のときは、上記と同様に書き込みアドレスとストアの対象となるレジスタをセットし、PCを更新します。~
最後に、ステージの処理をmemexステージにリレーします。その際、tstoreというタスクで起動しています。~
これはmemexステージにおいて、tstore信号がアサートされている状態になることを示します。~
~
LD命令のtloadと、ST命令のtstoreをこのように区別しておくことで、memexステージにおける処理を判定しています。
dHLT : par {
hlt();
finish;
}
HLT命令のときは、execステージの処理を終了させ、CPUを停止させます。~
hltによってその制御が出力され、シミュレーションを終了させることができます。
else : par {
npc2pc();
wb();
relay ifetch.ift();
}
}
上記の命令に該当しなかった場合は、PCを更新して、ステージの処理をifetchステージにリレーさせます。
opr1set();
opr1setによってR2が指すレジスタのデータをレジスタファイルからopr1に読み出します。~
opr1はsnxモジュールに定義されています。
any {
ITYPE : I2clain1();
else : opr12clain1();
}
I形式の命令かを判断し、その場合、I2clain1によって加算器の一方の入力に即値を使用します。~
そうでない場合は加算器の一方の入力にopr1の値を使用します。
any {
ITYPE & (R3 == 0b00) : opr2zero();
else : opr2reg();
}
I形式の命令で、R3が0である場合opr2zeroによってopr2に0をセットします。~
そうでない場合はR3が指すレジスタのデータをレジスタファイルからopr2に読み出します。
any {
(ITYPE | dADD) : aluadd();
dSLT : aluslt();
dAND : aluand();
dNOT : alunot();
dSR : alusr();
}
この部分がALUとなります。~
I形式かADD命令の場合はaluaddによって加算を、SLT命令のときはalusltによって大小比較を、~
AND命令のときはaluandによって論理積を、NOT命令のときはalunotによって論理否定を、SR命令のときはalusrによって右シフトを行います。
any {
^ITYPE : rwb();
dLDA : ldawb();
dBAL : balwb();
}
}
}
rwb, ldawb, balwbは、レジスタファイルに値を書き込む処理をそれぞれ行います。~
I形式の命令の場合はR1が指すレジスタにALUの出力を、LDA命令の場合はR2が指すレジスタにALUの出力を書き込みます。~
BAL命令の場合はというと、R2が指すレジスタにインクリメントしたPCの値を書き込みます。~
~
これは、命令のアドレスの戻り値となります。~
簡単に説明すると、関数の呼出の際にそのアドレスを記憶しておいて、関数の動作が終了すると、そのアドレスに戻ってくるような動作を実現できます。~
~
以上でexecステージの記述が終了しました。~
上から逐次的に解説していきましたが、各処理が並行に動いていることを忘れないでください。
stage memex {
par {
any {
memex.tstore : store();
memex.tload : load();
}
relay ifetch.ift();
wb();
}
}
}
最後にmemexステージです。~
memexステージを起動する際に、LD命令とST命令でtstore, tloadというタスクの区別をしました。~
ここではそのtstore, tloadを判定して、storeによってメモリへの書き込み、loadによってメモリからの読み込みを行っています。~
~
メモリアクセス終了後、ステージ処理をifetchステージにリレーさせます。~
LD命令とST命令は3サイクルの命令となっています。~
~
以上で、snxctlモジュールの解説は終了になります。
***snxモジュール [#p1619ad5]
CPUのコアとなるsnxモジュールです。~
snxctlで使用されていた制御の動作の実体が定義されています。
module snx {
input inst<16>;
input datai<16>;
output datao<16>;
output iadrs<16>;
output adrs<16>;
instrout inst_read;
instrout inst_write;
instrout memory_read;
instrout memory_write;
instrout wb;
instrout hlt;
入出力を定義しています。
reg_wr pc<16>;
16ビットのPC(プログラムカウンタ)用のレジスタです。
reg_wr st0;
reg st1, st2;
このレジスタはstart信号を一度だけアサートさせるために使用します。
snxctl ctl;
inc16 inc;
cla16 cla;
reg4 gr;
snxに必要なサブモジュールを宣言しています。
reg opreg<16>, mar<16>, regnum<2>;
命令コード用、メモリアドレス用、メモリアクセスの際に操作するレジスタの指定用のレジスタです。
sel opr1<16>, opr2<16>, nxpc<16>;
sel aluo<16>, clain1<16>;
snxで使用する内部端子の定義です。説明は省略します。
instr_arg memory_write(adrs,datao);
instr_arg memory_read(adrs);
instr_arg inst_read(iadrs);
制御出力端子の引数を宣言します。~
これは制御出力端子をアサートする際に渡した引数をoutputに接続するという動作をします。
par {
st0 := 0b1;
st1 := st0;
st2 := st1;
if((st2 == 0b0) & (st1 == 0b1)) ctl.start();
この部分は一度だけ、ctl(snxctl)のstartをアサートする記述です。
ctl.op = opreg;
ctl.opr1zero = (opr1 == 0x0000);
}
ctl(snxctl)の入力opにopregを、opr1zeroに(opr == 0x0000)をそれぞれ入力させています。
instruct ctl.setop opreg := inst_read(pc).inst;
ctl(snxctl)のsetopがアサートされたとき、pcのアドレスの命令コードを命令メモリから読み出して、opregに書き込みます。~
ifetchステージで行われる命令フェッチの処理です。
instruct ctl.npc nxpc = inc.do(pc).out;
npcがアサートされたとき、pcをインクリメントした値をnxpcにセットします。
instruct ctl.npc2pc pc := nxpc;
npc2pcがアサートされたとき、nxpcの値でpcを更新します。
instruct ctl.alu2pc pc := aluo;
alu2pcがアサートされたとき、aluoの値でpcを更新します。~
これは分岐が成立したときに呼び出される処理です。
instruct ctl.wb wb();
wbがアサートされたとき、データが更新されたことを知らせる制御出力端子wbをアサートします。~
今回の設計では無視しても問題ありません。
instruct ctl.hlt hlt();
hltがアサートされたとき、CPUを停止したことを知らせる制御出力端子hltをアサートします。~
シミュレーションを止めるために使用しています。
instruct ctl.cla2mar mar := cla.out;
cla2marがアサートされたとき、claの出力であるcla.outでmarを更新します。
instruct ctl.r2grnum regnum := dR2;
r2grnumがアサートされたとき、dR2(opcode<11:10>)でregnumを更新します。~
これはR2で指定したレジスタの番号をregnumに格納しています
instruct ctl.opr1set opr1 = gr.reada(dR2).outa;
opr1setがアサートされたとき、レジスタファイル(gr)からdR2(opcode<11:10>)で指定されたのレジスタに対応するデータを読み出して、opr1にセットします。
instruct ctl.I2clain1 clain1 = dI;
I2clain1がアサートされたとき、dIをclain1にセットします。
instruct ctl.opr12clain1 clain1 = opr1;
opr12clain1がアサートされたとき、opr1をclain1にセットします。
instruct ctl.opr2zero opr2 = 0x0000;
opr2zeroがアサートされたとき、0をopr2にセットします。
instruct ctl.opr2reg opr2 = gr.readb(dR3).outb;
opr2regがアサートされたとき、レジスタファイル(gr)からdR3(opcode<9:8>)で指定されたのレジスタに対応するデータを読み出して、opr2にセットします。
instruct ctl.aluadd aluo = cla.do(0b0, clain1, opr2).out;
aluaddがアサートされたとき、clain1とopr2を加算した結果をaluoにセットします。
instruct ctl.aluslt par {
cla.do(0b1, clain1, ^opr2);
aluo = 15#0b0 ||
((clain1<15> & ^opr2<15>)|
(cla.out<15> & ^clain1<15> & ^opr2<15>)|
(cla.out<15> & clain1<15> & opr2<15>));
}
alusltがアサートされたとき、clain1とopr2の否定の減算処理を行い、大小判定した結果をaluoにセットします。
instruct ctl.aluand aluo = opr1 & opr2;
aluandがアサートされたとき、opr1とopr2の論理積の結果をaluoにセットします。
instruct ctl.alunot aluo = ^opr1;
alunotがアサートされたとき、opr1の論理否定をaluoにセットします。
instruct ctl.alusr aluo = 0b0 || opr1<15:1> ;
alusrがアサートされたとき、opr1を右シフトした結果をaluoにセットします。
instruct ctl.rwb gr.write(dR1,aluo);
rwbがアサートされたとき、aluoの値をレジスタファイル(gr)のdR1(opreg<7:6>)で指定されたのレジスタに書き込みます。
instruct ctl.ldawb gr.write(dR2, aluo);
ldawbがアサートされたとき、nxpcの値をレジスタファイル(gr)のdR2(opreg<11:10>)で指定されたのレジスタに書き込みます。
instruct ctl.balwb gr.write(dR2, nxpc);
balwbがアサートされたとき、nxpcの値をレジスタファイル(gr)のdR2(opreg<11:10>)で指定されたのレジスタに書き込みます。
instruct ctl.store memory_write(mar,gr.reada(regnum).outa);
storeがアサートされたとき、レジスタファイル(gr)のregnum(dR2(opcode<11:10>))で指定されたのレジスタの値をデータメモリのmar番地に書き込みます。
instruct ctl.load gr.write(regnum, memory_read(mar).datai);
}
loadがアサートされたとき、データメモリのmar番地のデータをレジスタファイル(gr)のregnum(dR2(opcode<11:10>))で指定されたのレジスタに書き込みます。
***reg4モジュール [#g931df3d]
内部に16ビットのレジスタを4つ持つレジスタファイルであるreg4モジュールです。
module reg4 {
input in<16>;
input inadr<2>;
input outaadr<2>;
input outbadr<2>;
output outa<16>;
output outb<16>;
instrin reada;
instrin readb;
instrin write;
入出力を定義しています。
reg r0<16>, r1<16>, r2<16>, r3<16>;
16ビットのレジスタを4つ定義しています。
instruct reada any {
outaadr == 0b00 : outa = r0;
outaadr == 0b01 : outa = r1;
outaadr == 0b10 : outa = r2;
outaadr == 0b11 : outa = r3;
}
制御入力端子readaがアサートされたとき、outaにoutaddrが示すレジスタのデータを出力します。
instruct readb any {
outbadr == 0b00 : outb = r0;
outbadr == 0b01 : outb = r1;
outbadr == 0b10 : outb = r2;
outbadr == 0b11 : outb = r3;
}
制御入力端子readbがアサートされたとき、outbにoutbddrが示すレジスタのデータを出力します。
instruct write any {
inadr == 0b00 : r0 := in;
inadr == 0b01 : r1 := in;
inadr == 0b10 : r2 := in;
inadr == 0b11 : r3 := in;
}
}
制御入力端子writeがアサートされたとき、inadrが示すレジスタにinのデータを書き込みます。~
これによって、読み出しポートを2つ、書き込みポートを1つ持つレジスタファイルを実現しています。
***inc16モジュール [#nb139142]
16ビットのPCをインクリメントするinc16モジュールです。~
circuitは内部で算術演算子を使用するときに使うモジュールの宣言方法です。
circuit inc16 {
input in<16>;
output out<16>;
instrin do;
入出力を定義しています。
instruct do out = in + 0x0001;
}
制御入力端子doがアサートされたとき、inをインクリメントした結果をoutに出力します。
***cla16モジュール [#la661995]
主な演算をする16ビットの加算器であるcla16モジュールです。
circuit cla16 {
input cin;
input in1<16>;
input in2<16>;
output out<16>;
instrin do;
入出力を定義しています。
instruct do out = in1 + in2 + (15#0b0 || cin);
}
制御入力端子doがアサートされたとき、in1, in2, cinを加算した結果をoutに出力します。~
ここでは合成の際にCarry lookahed adderが出力されるようになっています。
ページ名: