JUMP!
沒有其他選擇,執行到這就一定得跳!
這次有兩種不同格式的指令,分別是 J-type 的 JAL,
以及 I-type 的 JALR。
另外要注意的是,
如果跳到不合法的位置會觸發 instruction-address-misaligned exception。
指令格式如下:
|31 12|11 7|6 0|
+-------------------------------------------+
| imm | rd | opcode |
+-------------------------------------------+
指令格式如下:
|31 20|19 15|14 12|11 7|6 0|
+-------------------------------------------+
| imm | rs1 | funct3 | rd | opcode |
+-------------------------------------------+
是一種依照目前指令所在的 Address + Offset 決定跳到哪的指令,
因為 RISC-V 支援 Compress Extension,
必須支援跳到的位置是 Half Word Alignment,
所以這道指令的 imm 是以 Half Word 當作單位,
要乘 2 才會是真正的 Offset。
另外要注意的是,只有在支援 Compress Extension 的時候,
跳到的位置限制才會放寬到 Half Word Alignment,
不支援的情況下要特別注意 Alignment 的問題。
rd = current_pc + 4
pc = current_pc + (imm * 2)
|31 12|11 7|6 0|
+-------------------------------------------+
| imm | rd | 1101111 |
+-------------------------------------------+
這邊跟上面不太一樣,是完全依照 register 的內容當成 base,
再加上 offset 決定最後跳到的位置,
特別注意的是 register 內的直就直接當作 address 用,
不用為了Alignment 另外乘 2,
也可以跟前面實作的 LUI 配合使用,
20 + 12 bit 的 imm 值可以跳到 Address Space 的任意位置。
當 rs1 設定為 x0 的時候,
只要單行指令跳到 address space 最高/最低的 2KiB 的任意位置,
可以支援簡單的 Run Time Library。
另外,雖然規格書明訂最後運算的結果會捨去最後一個 bit,
使用上還是和 JAL 一樣要注意 Alignment 的問題!
rd = current_pc + 4
pc = (rs1 + imm) & (~0x1)
|31 20|19 15|14 12|11 7|6 0|
+-------------------------------------------+
| imm | rs1 | 000 | rd | 1100111 |
+-------------------------------------------+
跟前面遇過的指令一樣,
這道指令也是 opcode 和 func3 都跟 JALR 一樣,
只是我們不需要回來的位置了,
所以把 rd 設定為 x0。
x0 = current_pc + 4
pc = (rs1 + imm) & (~0x1)
|31 20|19 15|14 12|11 7|6 0|
+-------------------------------------------+
| imm | rs1 | 000 | 0 | 1100111 |
+-------------------------------------------+
github 頁面 Tag: ITDay18
糟糕,JAL 的 imm 格式讓實作變得有點髒兮兮的了,
幸好偉大的 sc_dt 提供方便的功能,
讓猴子寫的 code 可以被未來的猴子看懂拉!
//before
auto offset = ((imm & ~0x7FFFF) | //imm[30:19] > offset[31:20]
(imm & 0xFF) << 11 | //imm[7:0] > offset[19:12]
(imm & (0x1 << 8)) << 2 | //imm[8] > offset[11]
(imm & (0x3FF << 9)) >> 9) //imm[18:9] > offset[10:1]
<< 1; //need to refactor to readable monkey style
//after
int32_t INSTRUCTION_DECODER::get_imm_j()
{
auto value = sc_dt::sc_int<32>();
value(20, 20) = instruction_value(31, 31);
value(19, 12) = instruction_value(19, 12);
value(11, 11) = instruction_value(20, 20);
value(10, 1) = instruction_value(31, 21);
value <<= 12;
value >>= 12;
return value;
}
把取得 offset
的功能搬到 Decoder 改寫後,
指令實作的部分就變得簡單很多。
//executor.cpp
...
case INSTRUCTION_DECODER_INTERFACE::JAL_OP:
JAL_E();
break;
case INSTRUCTION_DECODER_INTERFACE::JALR_OP:
switch (instruction_decoder->get_func3()) {
case INSTRUCTION_DECODER_INTERFACE::JALR_FN3:
JALR_E();
break;
default:
std::cout << "INVALID: Func3 in JALR_OP :" << instruction_decoder->get_func3() << std::endl;
break;
}
...
void EXECUTOR::JAL_E()
{
auto offset = instruction_decoder->get_imm_j();
auto rd = instruction_decoder->get_rd();
register_file->set_value_integer(rd, new_pc);
new_pc = register_file->get_pc() + offset;
}
void EXECUTOR::JALR_E()
{
auto offset = instruction_decoder->get_imm(31, 20);
auto rs1 = instruction_decoder->get_rs1();
auto rd = instruction_decoder->get_rd();
register_file->set_value_integer(rd, new_pc);
new_pc = (register_file->get_pc() + offset) & ~0x1;
}
//instructionDecoderInterface.h
...
JAL_OP = 0b1101111,
JALR_OP = 0b1100111,
...
JALR_FN3 = 0b000,
...
偷偷跟你們說個秘密:
前天有隻猴子犯了小小的錯誤,
我偷偷笑了很久,很缺德,現在在反省,
這件事不要跟其他人說喔!
前面的部分省略,
因為把 imm 設定為 -2,乘 2 之後就是前一道指令,
可以看到 JAL 執行之後又跳回 SH 再執行一次。
$ make run
...
LHU
rs1: 0
rd: 8
addr: 4
value: 33043
SH
rs1: 0
rs2: 7
addr: 1024
JAL
rd: 0
new_pc: 48
SH
rs1: 0
rs2: 7
addr: 1024
JAL
rd: 0
new_pc: 48