对NEMU如何执行一条指令的理解.
本文章为一生一芯教学项目的个人理解,本人并不能保证其正确,请注意学术诚信 笔者并非计算机科班,内容难免存在错误,如您发现,欢迎与我联系.
0 主要的数据结构 注意:snpc和dnpc:
snpc: static next pc,二进制代码中的下一个pc
dnpc:dynamic next pc,下一个程序执行的pc 例如, 100: jmp 102 101: add 102: xor 100 的 snpc 为 101,100 的 dnpc 为 102。 因此,我们在执行指令时应该使用dnpc来更新pc并维护dnpc。
1 2 3 4 5 6 7 8 typedef struct Decode { vaddr_t pc; vaddr_t snpc; vaddr_t dnpc; ISADecodeInfo isa; IFDEF(CONFIG_ITRACE, char logbuf[128 ]); } Decode;
1 初始化 更新pc与snpc
1 2 3 s->pc = pc; s->snpc = pc; isa_exec_once(s);
2 取指 NEMU在内存中读指令(4字节)并更新snpc
1 s->isa.inst = inst_fetch(&s->snpc, 4 );
1 2 3 4 5 static inline uint32_t inst_fetch (vaddr_t *pc, int len) { uint32_t inst = vaddr_ifetch(*pc, len); (*pc) += len; return inst; }
3 译码 NEMU通过模式匹配,匹配到指令结构
1 2 3 4 INSTPAT_START(); INSTPAT("??????? ????? ????? ??? ????? 00101 11" , auipc , U, R(rd) = s->pc + imm); INSTPAT_END();
1 2 3 4 5 6 7 8 9 10 #define INSTPAT(pattern, ...) do { \ uint64_t key, mask, shift; \ pattern_decode(pattern, STRLEN(pattern), &key, &mask, &shift); \ if ((((uint64_t )INSTPAT_INST(s) >> shift) & mask) == key) { \ INSTPAT_MATCH(s, ##__VA_ARGS__); \ goto *(__instpat_end); \ } \ } while (0 )
3.1 匹配模式过程 pattern_decode(pattern, STRLEN(pattern), &key, &mask, &shift);
用于对模式解码并得到键码,掩码与偏移。
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 / --- pattern matching mechanism --- __attribute__((always_inline))static inline void pattern_decode (const char *str, int len, uint64_t *key, uint64_t *mask, uint64_t *shift) { uint64_t __key = 0 , __mask = 0 , __shift = 0 ;#define macro(i) \ if ((i) >= len) goto finish; \ else { \ char c = str[i]; \ if (c != ' ' ) { \ Assert(c == '0' || c == '1' || c == '?' , \ "invalid character '%c' in pattern string" , c); \ __key = (__key << 1 ) | (c == '1' ? 1 : 0 ); \ __mask = (__mask << 1 ) | (c == '?' ? 0 : 1 ); \ __shift = (c == '?' ? __shift + 1 : 0 ); \ } \ }#define macro2(i) macro(i); macro((i) + 1) #define macro4(i) macro2(i); macro2((i) + 2) #define macro8(i) macro4(i); macro4((i) + 4) #define macro16(i) macro8(i); macro8((i) + 8) #define macro32(i) macro16(i); macro16((i) + 16) #define macro64(i) macro32(i); macro32((i) + 32) macro64(0 ); panic("pattern too long" );#undef macro finish: *key = __key >> __shift; *mask = __mask >> __shift; *shift = __shift; }
得到了特定指令的匹配规律,我们就可以对取到的指令进行逐一匹配了。
3.2 指令匹配过程 if ((((uint64_t)INSTPAT_INST(s) >> shift) & mask) == key) { \
这条if语句对取到的指令进行匹配, 如符合,则执行INSTPAT_MATCH(s, ##__VA_ARGS__); \
。 这个宏在decode_exec中定义。
1 2 3 4 5 6 #define INSTPAT_MATCH(s, name, type, ... ) { \ int rd = 0; \ word_t src1 = 0, src2 = 0, imm = 0; \ decode_operand(s, &rd, &src1, &src2, &imm, concat(TYPE_, type)); \ __VA_ARGS__ ; \ }
3.3 立即数译码过程 这里的decode_operand用来对立即数进行译码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 enum { TYPE_I, TYPE_U, TYPE_S, TYPE_N, };#define src1R() do { *src1 = R(rs1); } while (0) #define src2R() do { *src2 = R(rs2); } while (0) #define immI() do { *imm = SEXT(BITS(i, 31, 20), 12); } while(0) #define immU() do { *imm = SEXT(BITS(i, 31, 12), 20) << 12; } while(0) #define immS() do { *imm = (SEXT(BITS(i, 31, 25), 7) << 5) | BITS(i, 11, 7); } while(0) static void decode_operand (Decode *s, int *rd, word_t *src1, word_t *src2, word_t *imm, int type) { uint32_t i = s->isa.inst; int rs1 = BITS(i, 19 , 15 ); int rs2 = BITS(i, 24 , 20 ); *rd = BITS(i, 11 , 7 ); switch (type) { case TYPE_I: src1R(); immI(); break ; case TYPE_U: immU(); break ; case TYPE_S: src1R(); src2R(); immS(); break ; case TYPE_N: break ; default : panic("unsupported type = %d" , type); } }
其中存在五个辅助宏,用于便捷的获取立即数。
4 执行 程序执行INSTPAT("??????? ????? ????? ??? ????? 00101 11", auipc , U, R(rd) = s->pc + imm);
中的R(rd) = s->pc + imm
指令。指令执行的阶段结束之后, decode_exec()函数将会返回0, 并一路返回到exec_once()函数中。
5 更新PC 上文已经提到snpc与dnpc的区别,在程序执行时我们需要正确维护dnpc。cpu.pc = s->dnpc;
来更新dnpc。