PA2-1-RTFSC理解NEMU指令执行过程

对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; // static next pc
vaddr_t dnpc; // dynamic next pc
ISADecodeInfo isa;
IFDEF(CONFIG_ITRACE, char logbuf[128]);
} Decode;

1 初始化

更新pc与snpc

1
2
3
s->pc = pc;
s->snpc = pc;// update s->pc and s->snpc.
isa_exec_once(s); // fetch and execute code s->pc and update s-> dnpc.

2 取指

NEMU在内存中读指令(4字节)并更新snpc

1
s->isa.inst = inst_fetch(&s->snpc, 4); // fetch inst in mem (4 bytes) and update s->snpc.
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
/* Here to decode */
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; \
/* Decode the pattern and get key code, mask and shift of the pattern. */
pattern_decode(pattern, STRLEN(pattern), &key, &mask, &shift); \
/* Get inst and if inst matches the pattern */
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与掩码__mask。
* __shift为LSB至opcode的距离位数。
*/
__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:
/*注意:这里,我们需要将键码、掩码和移位移动到正确的位置。
* 移位是从 LSB 到操作码的位数,用于匹配操作码。
* 密钥和掩码通过移位位向右移动。
* 例如,“?????? ?????? ?????? ??? ?????? 00101 11”将被解码为
* key = 0x17,mask = 0x7f,移位 = 0。
*/
*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, ... /* execute body */ ) { \
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, // none
};

#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。


PA2-1-RTFSC理解NEMU指令执行过程
http://example.com/2025/02/26/PA2-1-RTFSC理解NEMU指令执行过程/
作者
lethe
发布于
2025年2月26日
许可协议