RISC-V: Jump 指令

JUMP!
没有其他选择,执行到这就一定得跳!
这次有两种不同格式的指令,分别是 J-type 的 JAL,
以及 I-type 的 JALR。

另外要注意的是,
如果跳到不合法的位置会触发 instruction-address-misaligned exception。

J-type

指令格式如下:

|31                      12|11   7|6       0|
+-------------------------------------------+
|           imm            |  rd  | opcode  |
+-------------------------------------------+

I-type

指令格式如下:

|31     20|19   15|14    12|11   7|6       0|
+-------------------------------------------+
|   imm   |  rs1  | funct3 |  rd  | opcode  |
+-------------------------------------------+

JAL

是一种依照目前指令所在的 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 |
+-------------------------------------------+

JALR

这边跟上面不太一样,是完全依照 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 |
+-------------------------------------------+

JR

跟前面遇过的指令一样,
这道指令也是 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

<<:  Day16:卡文一篇,难解

>>:  Day17 - 帮蛇多加了暂停与继续

Android Studio初学笔记-Day2-LinearLayout介绍

对於初学着来说(像我本人),一开始如果没先了解布局的运作容易在布局的编排上产生问题,所以第一篇先来介...

Day-24 AlertDialog

AlertDialog与Toast皆可用於显示讯息, 但与Toast不同的是, AlertDialo...

聊聊structure concurrency 结构化并发

前面我们谈了,coroutine的coroutineScope、继承、异常处理和取消,也在文中提到了...

[2021铁人赛 Day18] General Skills 15

引言 我们终於快完成 General Skills 了, 最後的时间我们会来解解其他六大类的题目。...

[Day 9] 练练CSS,「Amos - 金鱼都能懂的网页切版 」实作练习

前言 相信很多刚开始接触CSS或想对切版有所认识,都知道Amos老师吧!! 非常非常感谢老师的系列教...