1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| 我们只需要一个32位的指令二进制串进行操作,为何要使用一个64位呢? 扩展性:使用INSTPAT宏定义指令模板时,我们也是用的64位来定义指令,这是给后续留出足够的扩展余地. 1ull << (bits) - 1 会生成一个低于bits位,全为1,其他全为0的掩码.
SEXT宏解析: 用于抓取立即数字段 定义了一个内嵌int64_t空间的结构体变量__x,其中包含len长度的n字段.其中将x赋值给n字段. 传参x是一个抓取完成的指令字段值.len是这个字段的长度.然后通过```.n = x```, 将这个32位有符号数x赋值给len位的有符号数n,然后n会被有符号扩展为__x,最后将__x.n无符号扩展为64位传出去. ***这里为何进行的是无符号扩展,据我所知,所有的立即数字段都是有符号数,如果抓取出来进行的是无符号扩展,会导致出错.故应该将```(uint64_t)__x.n;```修改为:```(int64_t)__x.n;```
### g_nr_guest_inst变量的解析 表示匹配/执行的指令数
### 关于pc递增 src/cpu文件定义的execute函数声明了Decode变量s,然后传给了exec_once. src/cpu文件定义的exec_once函数将全局变量pc初始化s->pc,s->snpc,然后调用isa_exec_once函数. 定义在src/isa/riscv32/instc文件的isa_exec_once函数接受这个s,然后读取指令二进制串(调用inst_fetch函数) 在inst_fetch函数中,s->snpc默认递增到下一条指令 然后调用decode_exec函数执行,然后就返回. 在decode_exec函数中,s->snpc赋值给s->dnpc. 然后isa_exec_once函数返回后,就是***cpu.pc = s->dnpc***,s->dnpc是下一条要执行的指令.
#### 关于snpc和dnpc实际差异地方 在src/isa/riscv32/inst.c文件的decode_exec函数中 先是将***s->dnpc = s->snpc*** 这是默认,但前执行的指令没有任何跳转,那么下一条真实执行指令默认就是snpc(snpc损失当前指令的下一条物理地址指令)
如果有跳转,那么会在指令exe阶段进行处理,此时dnpc会发生改变,其值自然会到正确的指令地址处.
最后在isa_exec_once退出,来到exec_once函数中,将***cpu.pc = s->dnpc***
### 关于如何添加指令 在src/isa/riscv32/inst.c文件中: 定义"do"宏
在enum枚举中,检查是否是扩展指令类型
添加新指令的立即数抓取宏,源寄存器读取宏(这个应该是不用的)
在decode_operand函数中,进行添加switch跳转条件.
在INSTPAT_START-INSTPAT_END代码块中添加指令模式
### 添加I(ADDi)型指令 检查:enum中,有TYPE_I型 确认:在immI宏中,读取的立即数字段通过查表,是ADDi中的立即数 检查:在decode_operand函数中TYPE_I操作正确
只需要在INSTPAT系列宏代码中添加关于opcode是0010011,funct3是000的指令模式
INSTPAT("??????? ????? ????? 000 ????? 00100 11", addi , I, R(rd) = src1 + imm);
### 关于li隐藏的可能bug 伪指令li,是加载立即数,关于立即数的大小.在12位有符号立即数范围内,那么这个指令会被进一步转换为addi指令,如果超过这个范围的立即数,该伪指令会进而转换为lui+addi指令.
所有对于li伪指令,我们需要对lui指令的实现作出及时的考虑.
### 关于实现jal指令 enum枚举中添加TYPE_J类型
定义immJ宏: imm[20|10:1|11|19:12] #define immJ() do {*imm = (SEXT(BITS(i, 31, 31), 1) << 19) | BITS(i, 19, 12) << 11 | BITS(i, 20, 20) << 10 | BITS(i, 30, 21);} while(0)
在decode_operand函数中添加对应的switch选项: case TYPT_J: immJ();break;
在INSTPAT代码块中实现指令模式:由于jal指令的"do"操作有多个部分,所以需要一个代码块来实现: { R(rd) = s->snpc; s->dnpc = s->pc + (imm << 1); } 这里的imm << 1;表示将立即数左移一位,这是指令体系结构中的设计,目的是为了节省存储不必要的低位0(地址是4对齐的,低位是0)
### 关于ret伪指令 ret指令是编译器指令,会被展开为jalr x0, 0(x1)
所以,需要实现jalr指令.
jalr是I型指令,太好实现了.
其"do"部分,第一步,将物理地址下一条指令存储在rd中,然后将立即数与src1相加,跳转.
###关于jal中的立即数抓取和左移问题 立即数在进行符号扩展,然后左移一位,为何要左移,原因已经说过.
但是4字节对齐的地址,为何是左移1位而不是2位.GPT4解释:因为指令在编码的时候,已经被左移了一位,剩下的需要移动的一位是在cpu内部进行移动.
### 从行为角度解释为何jal中立即数要左移,jalr中立即数不需要移动 定义中,很清楚,jal中的pc值是"干净"的,而立即数是不干净的,所以需要处理立即数.
jalr中的寄存器值,不一定是"干净"的,哪怕将立即数处理"干净"了. 所有没必要处理立即数.
直接将jalr中的立即数将寄存器值相加,得到跳转地址.
### INSTPAT宏的do传参修改以及INSTPAT\_MATCH中的_\_VA_ARGS_\_宏的修改 在编写jal指令的"do"的时候,我需要将代码以代码块的形式编写.
但是在INSTPAT_MATCH中的变参:
|