概述
1
实验目的
1.1
1. 熟悉CPU执行指令的步骤
2. 探究程序运行时的环境
3. 探究 diff-test
4. 探究模拟输入输出
实验内容
1.2
1. 在NEMU实现部分指令,运行第一个C程序 dummy
2. 实现更多的指令,能运行所有的 cputest
3. 实现输入输出,运行打字小游戏
阶段一
2
指令执行过程
2.1
在进行阶段一之前,我们已经了解了TRM的工作方式
冯诺依曼体系结构的核心思想就是“存储程序,程序控制”,其实就是取出PC所指向的指令从
内存读入到CPU中;CPU通过查表的方式得知这条指令的操作数与操作码;CPU往计算部件
里输入操作数就能得到执行结果并写回目的操作数;更新PC指向下一个指令的位置。对应
在NEMU中就是调用 instr_fetch() 函数取指;在 opcode_table 里面通过 IDEX() 与
IDEXW() 寻找对应的译码辅助函数 make_DHelper() 与执行辅助函数 make_EHelper 以及操
作数宽度,然后进行译码;执行 make_EHelper 里的函数执行相应的指令;调用 update_pc
更新PC。
问题一:请整理一条指令在NEMU中的执行过程.
答:
1. 在NEMU中主要通过 exec_once() 来执行PC所指向的指令。
2. 将当前的PC保存在全局译码信息 decinfo 的成员 seq_pc 中
3. 然后把 decinfo.seq_pc 的地址传送给 isa_exec()
4. 然后调用 instr_fetch() 去内存取出指令,并获得 opcode ,放入
decinfo.opcode 中
5. 根据 opcode 在 opcode_table 中进行索引,获取的元素将被作为参数调用
idex() 函数来进行译码与执行。
6. idex() 调用译码辅助函数 make_DHelper(name) 来获取指令中的操作数信息
7. idex() 调用执行辅助函数 make_EHelper(name) 使用RTL指令来执行该指令本
身的功能,并修改相应的寄存器的值。
8. 回到 exec_once() 执行 update_pc() 来让PC指向下一条动态指令的地址
while (1) {
从PC指示的存储器位置取出指令;
执行指令;
更新PC;
}
9. 循环。
运行第一个C程序
2.2
在 nexus-am/tests/cputest/ 目录下键入
可以看到提示 invaild opcode ,
1. 去 nexus-am/tests/cputest/build/dummy-$ISA-nemu.txt 中查看对应PC对应的指
令
2. 然后去查看i386手册找打对应的指令
3. 去 all-instr.h 中填充指令
4. 到 exec.c 中填充 opcode_table 里对应的 idex() 函数,选择对应的译码与执行函
数
5. 到对应的执行函数文件里修改执行函数
6. 如果需要RTL指令,还需要到 rtl.h 中实现
call
2.2.1
call 在指令中的位置为E8,
指令详情如下:
执行操作如下:
1. all-instr.h 中添加
2. 修改 opcode_table[] :
make ARCH=$ISA-nemu ALL=dummy run
make_EHelper(call);
/* 0xe8 */ IDEX(J, call), EMPTY, EMPTY, EMPTY,
3. 因为 call 为跳转指令,所以在 control.c 中实现
4. 需要用到 rtl_push() ,到 rtl.h 中实现
push
2.2.2
重复上面的步骤
1. all-instr.h 中添加
2. 修改 opcode_table[]
3. 因为 push 是数据移动指令,在 data-mov.c 中实现
pusha
2.2.3
重复上面的步骤
make_EHelper(call)
{
// the target address is calculated at the decode stage
decinfo.is_jmp = 1;
rtl_push(&decinfo.seq_pc);
rtl_j(decinfo.jmp_pc);
print_asm("call %x", decinfo.jmp_pc);
}
static inline void rtl_push(const rtlreg_t *src1)
{
// esp <- esp - 4
cpu.esp-=4;
// M[esp] <- src1
rtl_sm(&cpu.esp, src1, 4);
}
make_EHelper(push);
/* 0x50 */ IDEX(r, push), IDEX(r, push), IDEX(r,
push), IDEX(r, push),
/* 0x54 */ IDEX(r, push), IDEX(r, push), IDEX(r,
push), IDEX(r, push),
/* 0x68 */ IDEX(push_SI, push), IDEX(I_E2G, imul2),
IDEXW(push_SI, push, 1), EMPTY,
make_EHelper(push) {
rtl_push(&id_dest->val);
print_asm_template1(push);
}
1. all-instr.h 中添加
2. 修改 opcode_table[]
3. 因为 pusha 是数据移动指令,在 data-mov.c 中实现
sub
2.2.4
重复步骤
1. all-instr.h 中添加
2. 修改 opcode_table[] ,手册上 opcode 为83是gp1,所以填充gp1中的指令
3. 修改 arith.c 中的函数
make_EHelper(pusha);
/* 0x60 */ EX(pusha), EX(popa), EMPTY, EMPTY,
make_EHelper(pusha) {
// TODO();
s0=cpu.pc;
rtl_push(&cpu.eax);
rtl_push(&cpu.ecx);
rtl_push(&cpu.edx);
rtl_push(&cpu.ebx);
rtl_push(&s0);
rtl_push(&cpu.ebp);
rtl_push(&cpu.esi);
rtl_push(&cpu.edi);
print_asm("pusha");
}
make_EHelper(sub);
make_group(gp1,
EX(add), EX(or), EX(adc), EX(sbb),
EX(and), EX(sub), EX(xor), EX(cmp))
/* 0x80 */ IDEXW(I2E, gp1, 1), IDEX(I2E, gp1), EMPTY,
IDEX(SI2E, gp1),
make_EHelper(sub)
{
rtl_sub(&s0, &id_dest->val, &id_src->val);
operand_write(id_dest, &s0);
if (id_dest->width != 4)
{
rtl_andi(&s0, &s0, 0xffffffffu >> ((4 - id_dest->width) *
8));
}