以下に課題実現のためのヒントを示す。
※質問・疑問は随時受け付ける。
// #define DELAYED_BRANCH
===== 変更前 =====
...
case 0x03: // JAL
#ifdef DELAYED_BRANCH
Regs[31] = NPC + 4;
#else
Regs[31] = NPC;
#endif
...
===== 変更後 =====
...
case 0x03: // JAL
Regs[31] = NPC + 4;
...
例えば、以下のような実装を行う。
***** mips::IF() *****
void
mips::IF()
{
cout << "---------- IF ----------" << endl;
// パイプラインストール処理
if (IF_stall_f) {
cout << "*** IF stalled ***" << endl;
// ISステージのストール時にアドレスが変化するので再設定)
if (!IS_stall_f) {
PC = calculate_nextPC(PC);
cout << " set new PC = " << hex << PC << endl;
}
return;
}
// キャッシュがレディでないときはリクエスト処理を次のサイクルに後回し
if (!icache->is_ready()) { // I-cache is not ready
cout << " *** I-cache is not ready ***" << endl;
return;
}
icache->request_read_word(PC);
sim_addr NPC = PC + 4;
// to next stage
IF_IS_reg.set_valid();
IF_IS_reg.set_PC(PC);
IF_IS_reg.set_NPC(NPC);
cout << "PC=" << hex << PC << endl;
cout << "NPC=" << hex << NPC << endl;
// 分岐先アドレスを計算してPCにセット
//
// next PC calculation
//
PC = calculate_nextPC(NPC);
}
sim_addr
mips::calculate_nextPC(sim_addr NPC)
{
sim_inst IR = IS_RR_reg.get_IR();
// ジャンプ/分岐命令でない場合はNPCの値を採用
if (!is_branch_inst(IR)) // not branch instruction
return NPC;
sim_addr PC = NPC;
sim_word A, B;
int offset = imm(IR) << 2;
NPC = IS_RR_reg.get_NPC();
switch (opcode(IR)) {
case 0x00: // JR or JALR
if (funct(IR) == 0x08 || funct(IR) == 0x09) {
// data forwarding
PC = select_ALUInput_A(IR, Regs[rs(IR)]);
}
break;
case 0x02: case 0x03: // J or JAL
PC = (NPC & 0xf0000000)|((IR & 0x03ffffff) << 2);
break;
case 0x04: // BEQ
A = select_ALUInput_A(IR, Regs[rs(IR)]);
B = select_ALUInput_B(IR, Regs[rt(IR)]);
if (A == B)
PC = NPC + offset;
break;
case 0x05: // BNE
A = select_ALUInput_A(IR, Regs[rs(IR)]);
B = select_ALUInput_B(IR, Regs[rt(IR)]);
if (A != B)
PC = NPC + offset;
break;
case 0x06: // BLEZ
A = select_ALUInput_A(IR, Regs[rs(IR)]);
if ((signed long)A <= 0)
PC = NPC + offset;
break;
case 0x07: // BGTZ
A = select_ALUInput_A(IR, Regs[rs(IR)]);
if ((signed long)A > 0)
PC = NPC + offset;
break;
default:
cout << "unknown branch instruction opcode="
<< hex << opcode(IR) << endl;
break;
}
cout << " nextPC = " << hex << PC << endl;
return PC;
}
ここまで出来たところで、制御ハザードに対応できるか動作テストを行う。
以下のテストプログラム test3 を使うとよい。
アセンブリソース/
逆アセンブルリスト/
ダンプテキストファイル
追加:
レジスタのアップデートのタイミングの問題への対処
WBステージで行なわれるレジスタの更新のタイミングと
RRステージで行なわれるレジスタの値読み出しのタイミングの扱いが、
配付資料に書かれているものとシミュレータで少し異なる。
シミュレータでは、WBステージで更新されたレジスタの値は次のサイクルでしか
読み出しができない。
一方、配付資料で説明されている方法では、現在のサイクルの前半でレジスタの値が
更新され、後半ではレジスタの値が読み出せるようになるので、
同じサイクル中に値が読み出せる。
そのため、RRステージで読み出されるレジスタの値が異なる場合が生じることになるため、
これに対処する必要がある。
そこで、レジスタの値更新処理について、以下の変更を行なう。
void
mips::pre_update_register()
{
int last_updated_regno = 0;
sim_inst ir = MA_WB_reg.get_IR();
if ((is_ALU_inst(ir) && !is_muldiv_inst(ir)) ||
is_mfhilo_inst(ir))
last_updated_regno = rd(ir);
else if (is_ALUimm_inst(ir) || is_load_inst(ir))
last_updated_regno = rt(ir);
else if (opcode(ir) == 0x00 && funct(ir) == 0x09) // JALR
last_updated_regno = rd(ir);
else if (opcode(ir) == 0x03) // JAL
last_updated_regno = 31;
// no action if the updated register No. is 0.
if (last_updated_regno == 0)
return;
if (last_updated_regno == rs(IS_RR_reg.get_IR())) {
RR_EX_reg.set_A(Regs[last_updated_regno]);
cout << " update RR_EX_reg's A="
<< hex << Regs[last_updated_regno] << endl;
}
if (last_updated_regno == rt(IS_RR_reg.get_IR())) {
RR_EX_reg.set_B(Regs[last_updated_regno]);
cout << " update RR_EX_reg's B="
<< hex << Regs[last_updated_regno] << endl;
}
}
// work-around code for register update timing problem
pre_update_register();
以上までの作業で、mipsy
シミュレータのパイプライン化はほぼ完了
である。
適当なプログラム(ある程度複雑な動作をするものがよい)を作って動作確認を
行うこと。