目录

  • 思考题
    • 1.什么是 API
    • 2.AM 属于硬件还是软件?
    • 3.堆和栈在哪⾥?
    • 4.回忆运⾏过程
    • 5.神奇的eflags(2)
    • 6.这是巧合吗?
    • 7.nemu的本质
    • 8.设备是如何⼯作的?
    • 9. CPU 需要知道设备是如何⼯作的吗?
    • 10. 什么是驱动?
    • 11. cpu知道吗?
    • 12.再次理解volatile
    • 13.hello world运⾏在哪⾥?
    • 14.如何检测很多个键同时被按下?
    • 15.编译与链接Ⅰ
    • 16.编译与链接Ⅱ(10分)
    • 17.I/O 端⼝与接⼝(10分)
  • 实验内容
    • 实现剩余所有 x86 指令(40 分)
      • lea 指令
      • 第一个 指令组 and add or adc sbb sub xor cmp 指令
      • jmp 指令
      • jbe 指令 long long
      • jmp 指令
      • jbe 指令 short
      • add 指令
      • test 指令
      • sete 指令
      • movzbl 指令
      • leave 指令
      • inc 指令
      • push Ib 指令
      • sar shl shr 指令
      • cltd 指令
      • not idiv div mul 指令
      • inc 指令
      • imul 指令
      • MOVSX 指令
      • jmp_rm call_rm指令
      • imul1 指令
    • 通过⼀键回归测试(5 分)
    • IN/OUT 指令(10 分)
    • 实现时钟设备(10 分)
      • 实现 _uptime() 函数
    • 运⾏跑分项⽬(10 分)
      • 运行dhrystone
      • 运行coremark
      • 运行microbench
    • 实现键盘设备(10 分)
    • 添加内存映射 I/O (10 分)
    • 运⾏打字⼩游戏(5 分)
  • References

课程文档:

2.2 基本指令集

项目地址:

GitHub - yym68686/ics2022: NUAA PA ics2022

思考题

1.什么是 API

应用程序接口(Application Programming Interface,又称为应用编程接口)是软件系统不同组成部分衔接的约定。
接口本身指一种规范或者说约定,用于说明供需的具体情况。

2.AM 属于硬件还是软件?

AM即不属于硬件也不属于软件,它只是一个抽象概念,描述了一个计算机应该具备的功能,或者说它描述的就是指令集体系本身。

3.堆和栈在哪⾥?

为什么堆和栈的内容没有放入可执行文件里面?

  • 因为堆本身就是运行时分配的内存

那程序运行时刻用到的堆和栈又是怎么来的?

  • 栈也是用来保存运行时环境变量和函数运行时栈帧的。程序运行时操作系统会在其进程空间通过对特定变量赋值来分配堆和栈。

4.回忆运⾏过程

make ALL=xxx run运行过程:

  • 首先读取$(AM_HOME)/Makefile.check中的默认参数,根据设置的ARCH指代架构
  • 然后通过命令行指定的ALL寻找tests目录下对应的.c文件
  • 根据AM中指定ARCH提供的编译链接规则编译生成可执行文件
  • 将可执行文件作为nemu的镜像启动nemu,控制权转交给nemu

5.神奇的eflags(2)

+-----+-----+------------------------------------+
| SF  |  OF |                 实例               |
+-----+-----+------------------------------------+
|  0  |  0  |               2 - 1                |
+-----+-----+------------------------------------+
|  0  |  1  |            (2 ^ 31) - 1            |
+-----+-----+------------------------------------+
|  1  |  0  |               1 - 2                |
+-----+-----+------------------------------------+
|  1  |  1  |         (2 ^ 31 - 1) - (-1)        |
+-----+-----+------------------------------------+

(OF || SF) == 0,被减数大于减数。

6.这是巧合吗?

  • aboveop2 > op1无符号比较.
  • belowop2 < op1无符号比较.
  • greater:op2 > op1`有符号比较.
  • lessop2 < op1有符号比较.

7.nemu的本质

io模块,显示模块等。

8.设备是如何⼯作的?

分配端口号给设备,通过命令去访问他们。

9. CPU 需要知道设备是如何⼯作的吗?

不需要,只要执行命令就行了。

10. 什么是驱动?

驱动是一个允许应用程序与硬件交互的程序,这种程序创建了一个硬件与硬件,或硬件与软件沟通的接口,经由主板上的总线或其它沟通子系统与硬件形成连接的机制,这样的机制使得硬件设备上的数据交换成为可能。

11. cpu知道吗?

不需要。

12.再次理解volatile

检测不到设备寄存器的变化会进入死循环。

13.hello world运⾏在哪⾥?

不一样。前者运行在主机上,后者运行在nemu上。

14.如何检测很多个键同时被按下?

可以维护一个队列,按下进入队列,松开离开队列。

15.编译与链接Ⅰ

  • 去掉static:无报错,但是产生可执行文件变大
  • 去掉inline:报错,在special.c中定义了但未使用相应rtl函数.这是因为special.cincludertl.h文件,所以预处理会将函数定义复制到源文件中,又因为没有使用所以报错
  • 去掉两者:报错,在所有目标文件中重复定义了相应rtl函数,同理是因为预处理

16.编译与链接Ⅱ(10分)

  • 29
  • 有58个,多了29个,每个包含common.hdebug.h头文件的源文件都会有一个dummy变量
  • 报错原因是重复定义变量,此前没报错是因为只声明未初始化为弱符号,初始化了的为强符号,多个强符号会发生重复定义错误

17.I/O 端⼝与接⼝(10分)

  • 系统I/O地址的范围是0000H->8000H
  • 变成16地址编线以后,端口的地址范围是8001H-FFFFH.1k = 0x400.

最常见的就是与显示器的通讯,CPU向显示器传输了数据和地址,而显示器向CPU返回状态信息。

实验内容

实现剩余所有 x86 指令(40 分)

先测试一下所有样例:

./runall.sh

add-longlong文件的测试没有通过,查看汇编文件nexus-am/tests/cputest/build/add-longlong-x86-nemu.txt,编译nemu:

clear && cd /home/yanyuming/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=add-longlong run

lea 指令

发现0x8d,即lea指令没有实现,查询i386手册:LEA Gv,M,i386手册第327页有具体的伪代码。在src/cpu/decode/decode.c中发现make_DHelper(lea_M2G)已经完成,直接填表:

/* 0x8c */    EMPTY, IDEX(lea_M2G, lea), EMPTY, EMPTY,

src/cpu/exec/all-instr.h加上函数声明,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=add-longlong run


成功运行lea指令。

第一个 指令组 and add or adc sbb sub xor cmp 指令

查表是Grpl Ev,Iv,在汇编代码里是and,到指令组表里找到and位置在第五个:

在src/cpu/exec/exec.c里填写第一个指令组,先全部填上:

/* 0x80, 0x81, 0x83 */
make_group(gp1,EX(add), EX(or), EX(adc), EX(sbb),EX(and), EX(sub), EX(xor), EX(cmp))
  • adc sbb sub 已经实现好了可以直接填写

查看i386手册第262页伪代码:

Operation
DEST ← DEST AND SRC;
CF ← 0;
OF ← 0;

使用vim /make_EHelper(and)/ ~/ics2022/nemu/**,寻找and执行函数,在src/cpu/exec/logic.c补全and执行函数:

make_EHelper(and) {rtl_and(&t1, &id_dest->val, &id_src->val); //目的操作数与源操作数operand_write(id_dest, &t1); //写入目的操作数rtl_update_ZFSF(&t1, id_dest->width); //更新ZFSF位t1 = 0;rtl_set_OF(&t1); //设置OF位为0rtl_set_CF(&t1); //设置CF位为0print_asm_template2(and);
}

src/cpu/exec/all-instr.h加上函数声明,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=add-longlong run

发现nemu与qemu结果不一样,具体是esp本来应该是0x00007bd0,现在却是0x000000d0,单步调试,每次执行指令都把nemu与qemu值输出,执行15条指令后发现:

1000a3:   83 e4 f0                              andl $0xf0,%esp

立即数0xf0没有进行有符号扩展,导致0x00007bd0 & 0x000000f0 = 0x000000d0,因此要修改make_DopHelper(SI)有符号立即数译码函数,主要是没有考虑到立即数的长度。在src/cpu/decode/decode.c把make_DopHelper(SI)修改为:

/* sign immediate */
static inline make_DopHelper(SI) {assert(op->width == 1 || op->width == 4);op->type = OP_TYPE_IMM;/* TODO: Use instr_fetch() to read `op->width' bytes of memory* pointed by `eip'. Interpret the result as a signed immediate,* and assign it to op->simm.*op->simm = ???*/if (op->width == 4)op->simm = instr_fetch(eip, op->width);else{t0 = (uint16_t)instr_fetch(eip, op->width);rtl_sext(&t1, &t0, op->width);op->simm = t1;}rtl_li(&op->val, op->simm);#ifdef DEBUGsnprintf(op->str, OP_STR_SIZE, "$0x%x", op->simm);
#endif
}

src/cpu/exec/arith.c中实现add执行函数,可以抄已经实现好的adc指令:

make_EHelper(add) {rtl_add(&t2, &id_dest->val, &id_src->val);operand_write(id_dest, &t2);// 更新ZF,SF标志位rtl_update_ZFSF(&t2, id_dest->width);// 更新CF标志位rtl_sltu(&t0, &t2, &id_dest->val);rtl_set_CF(&t0);// 更新OF标志位rtl_xor(&t0, &id_dest->val, &id_src->val);rtl_not(&t0);rtl_xor(&t1, &id_dest->val, &t2);rtl_and(&t0, &t0, &t1);rtl_msb(&t0, &t0, id_dest->width);rtl_set_OF(&t0);print_asm_template2(add);
}

在/src/cpu/exec/logic.c里补全xor执行函数:

make_EHelper(xor) {rtl_xor(&t0, &id_dest->val, &id_src->val);operand_write(id_dest, &t0); //写入目的操作数t1 = 0;rtl_set_OF(&t1);rtl_set_CF(&t1);rtl_update_ZFSF(&id_dest->val, &id_dest->width);print_asm_template2(xor);
}

在src/cpu/exec/arith.c补全cmp指令,跟sub指令一摸一样,仅仅少了一个赋值过程:

make_EHelper(cmp) {rtl_sub(&t3, &id_dest->val, &id_src->val);// 更新ZF,SF标志位rtl_update_ZFSF(&t3, id_dest->width); // rtl_update_ZFSF函数内部临时变量是t0,所以不能用t0传参,否则更新SF会出错,因为更新ZF时,t0会变// 更新CF标志位rtl_sltu(&t1, &id_dest->val, &t3);rtl_set_CF(&t1);// 更新OF标志位rtl_xor(&t1, &id_dest->val, &id_src->val);rtl_xor(&t2, &id_dest->val, &t3);rtl_and(&t0, &t1, &t2);rtl_msb(&t0, &t0, id_dest->width);rtl_set_OF(&t0);print_asm_template2(cmp);
}

保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=add-longlong run

  • 成功执行and函数
  • 0xe9指令还没有完成

jmp 指令

查看汇编文件,0xe9是jmp指令,直接填表:

/* 0xe8 */    IDEXW(I, call, 4), IDEX(J,jmp), EMPTY, EMPTY,

src/cpu/exec/all-instr.h加上函数声明,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=add-longlong run

  • 成功执行jmp指令
  • 0x83指令没有完成

jbe 指令 long long

0x0f是扩展操作数,继续向后读一个字节,才能确定是什么指令,查表第415页,0x86代表jbe指令,0x80到0x8f都是跳转指令,而且长度都是四个字节,在src/cpu/exec/control.c里jcc函数已经定义好了。所以直接填表就好了。操作码后面跟了四个字节:

100158:   0f 86 68 ff ff ff       jbe    1000c6 <main+0x27>

默认长度就是4字节,所以直接用IDEX函数就行,补全操作码表:

/* 0x80 */    IDEX(J, jcc), IDEX(J, jcc), IDEX(J, jcc), IDEX(J, jcc),
/* 0x84 */    IDEX(J, jcc), IDEX(J, jcc), IDEX(J, jcc), IDEX(J, jcc),
/* 0x88 */    IDEX(J, jcc), IDEX(J, jcc), IDEX(J, jcc), IDEX(J, jcc),
/* 0x8c */    IDEX(J, jcc), IDEX(J, jcc), IDEX(J, jcc), IDEX(J, jcc),

src/cpu/exec/all-instr.h加上函数声明,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=add-longlong run

发现rtl_setcc函数没有实现,找到src/cpu/exec/cc.c,补全函数:

 switch (subcode & 0xe) {case CC_O:  //0rtl_get_OF(dest);break;case CC_B:  //2rtl_get_CF(dest); //小于,判断CFbreak;case CC_E:  //4rtl_get_ZF(dest);break;case CC_BE: //6rtl_get_CF(&t0);rtl_get_ZF(&t1);rtl_or(dest, &t0, &t1); //小于等于,CF和ZF至少一个等于1break;case CC_S:  //8rtl_get_SF(dest);break;case CC_L:  //12rtl_get_SF(&t0);rtl_get_OF(&t1);rtl_xor(dest, &t1, &t0); //带符号小于,SF不等于OFbreak;case CC_LE: //14rtl_get_ZF(&t0);rtl_get_SF(&t1);rtl_get_OF(&t2);rtl_xor(&t3, &t1, &t2);rtl_or(dest, &t0, &t3); //带符号小于等于,ZF=1或SF不等于OFbreak;default: panic("should not reach here");case CC_P: panic("n86 does not have PF");}

保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=add-longlong run

  • 成功运行jbe指令
  • 0xeb指令还没有实现

jmp 指令

0xeb是jmp指令,指令为:

1000cd:   eb 78                   jmp    100147 <main+0xa8>

eb操作码后面只有一个字节,所以在填写操作码表的时候需要指定字节数:

/* 0xe8 */    IDEXW(I, call, 4), IDEX(J, jmp), EMPTY, IDEXW(J, jmp, 1),

保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=add-longlong run

  • 成功执行jmp指令
  • 0x76指令没有实现

jbe 指令 short

操作码后面跟了一个字节:

 10014d:   76 80                   jbe    1000cf <main+0x30>

所以需要用IDEXW函数来指定长度,补全操作码表,查阅i386手册,0x70-0x7f都是jbe short指令:

/* 0x70 */    IDEXW(J, jcc, 1), IDEXW(J, jcc, 1), IDEXW(J, jcc, 1), IDEXW(J, jcc, 1),
/* 0x74 */    IDEXW(J, jcc, 1), IDEXW(J, jcc, 1), IDEXW(J, jcc, 1), IDEXW(J, jcc, 1),
/* 0x78 */    IDEXW(J, jcc, 1), IDEXW(J, jcc, 1), IDEXW(J, jcc, 1), IDEXW(J, jcc, 1),
/* 0x7c */    IDEXW(J, jcc, 1), IDEXW(J, jcc, 1), IDEXW(J, jcc, 1), IDEXW(J, jcc, 1),

保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=add-longlong run

  • 成功运行jbe指令
  • 0x01指令未实现

add 指令

查表可得0x00-0x05都是add指令。

根据表中数据,填写操作码表,因为0x08~0x3d除了指令名字不一样,其他都一样,顺便全补上:

/* 0x00 */    IDEXW(G2E, add, 1), IDEX(G2E, add), IDEXW(E2G, add, 1), IDEX(E2G, add),
/* 0x04 */    IDEXW(I2a, add, 1), IDEX(I2a, add), EMPTY, EMPTY,
/* 0x08 */    IDEXW(G2E, or, 1), IDEX(G2E, or), IDEXW(E2G, or, 1), IDEX(E2G, or),
/* 0x0c */    IDEXW(I2a, or, 1), IDEX(I2a, or), EMPTY, EX(2byte_esc),
/* 0x10 */    IDEXW(G2E, adc, 1), IDEX(G2E, adc), IDEXW(E2G, adc, 1), IDEX(E2G, adc),
/* 0x14 */    IDEXW(I2a, adc, 1), IDEX(I2a, adc), EMPTY, EMPTY,
/* 0x18 */    IDEXW(G2E, sbb, 1), IDEX(G2E, sbb), IDEXW(E2G, sbb, 1), IDEX(E2G, sbb),
/* 0x1c */    IDEXW(I2a, sbb, 1), IDEX(I2a, sbb), EMPTY, EMPTY,
/* 0x20 */    IDEXW(G2E, and, 1), IDEX(G2E, and), IDEXW(E2G, and, 1), IDEX(E2G, and),
/* 0x24 */    IDEXW(I2a, and, 1), IDEX(I2a, and), EMPTY, EMPTY,
/* 0x28 */    IDEXW(G2E, sub, 1), IDEX(G2E, sub), IDEXW(E2G, sub, 1), IDEX(E2G, sub),
/* 0x2c */    IDEXW(I2a, sub, 1), IDEX(I2a, sub), EMPTY, EMPTY,
/* 0x30 */    IDEXW(G2E, xor, 1), IDEX(G2E, xor), IDEXW(E2G, xor, 1), IDEX(E2G, xor),
/* 0x34 */    IDEXW(I2a, xor, 1), IDEX(I2a, xor), EMPTY, EMPTY,
/* 0x38 */    IDEXW(G2E, cmp, 1), IDEX(G2E, cmp), IDEXW(E2G, cmp, 1), IDEX(E2G, cmp),
/* 0x3c */    IDEXW(I2a, cmp, 1), IDEX(I2a, cmp), EMPTY, EMPTY,
  • add adc sbb and sub cmp xor已经实现好了,可以直接填写

在/src/cpu/exec/logic.c完成or执行函数:

make_EHelper(or) {rtl_or(&t2, &id_dest->val, &id_src->val);operand_write(id_dest, &t2);rtl_update_ZFSF(&t2, id_dest->width);rtl_set_OF(&tzero);rtl_set_CF(&tzero);;print_asm_template2(or);
}

src/cpu/exec/all-instr.h加上函数声明,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=add-longlong run
  • 成功执行add指令

test 指令

查表发现0x84,0x85,0xa8,0xa9都是test函数,填写操作码表:

     /* 0x84 */ IDEXW(G2E, test, 1), IDEX(G2E, test), EMPTY, EMPTY,/* 0xa8 */ IDEXW(I2a, test, 1), IDEX(I2a, test), EMPTY, EMPTY,

在/src/cpu/exec/logic.c里补全test函数:

make_EHelper(test) {rtl_and(&t1, &id_dest->val, &id_src->val);rtl_update_ZFSF(&t1, id_dest->width); //千万不要用t0,因为更新ZF时会用到t0,会把一开始的t0值覆盖rtl_set_CF(&tzero);rtl_set_OF(&tzero);print_asm_template2(test);
}

src/cpu/exec/all-instr.h加上函数声明,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=add-longlong run

  • 成功执行test函数
  • 0x0f指令未完成

sete 指令

遇到0f指令说明要看第二个字节,查表找到Byte Set on condition,从0x90-0x9f都是set操作,根据汇编代码:

 100132:   0f 94 c0                sete   %al

setcc指令长度为一。填表:

/* 0x90 */    IDEXW(E, setcc, 1), IDEXW(E, setcc, 1), IDEXW(E, setcc, 1), IDEXW(E, setcc, 1),
/* 0x94 */    IDEXW(E, setcc, 1), IDEXW(E, setcc, 1), IDEXW(E, setcc, 1), IDEXW(E, setcc, 1),
/* 0x98 */    IDEXW(E, setcc, 1), IDEXW(E, setcc, 1), IDEXW(E, setcc, 1), IDEXW(E, setcc, 1),
/* 0x9c */    IDEXW(E, setcc, 1), IDEXW(E, setcc, 1), IDEXW(E, setcc, 1), IDEXW(E, setcc, 1),

执行函数已经玩好了,不需要再写。在src/cpu/exec/all-instr.h加上函数声明,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=add-longlong run

  • 成功执行setcc函数
  • 0xb6指令没有实现

movzbl 指令

查表发现0xb6,0xb7,都movzx指令,一个宽度为1,一个宽度为2,填写操作码表:

 /* 0xb4 */    EMPTY, EMPTY, IDEXW(mov_E2G, movzx, 1), IDEXW(mov_E2G, movzx, 2),

执行函数已经完成,在src/cpu/exec/all-instr.h加上函数声明,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=add-longlong run

  • 成功执行movzx函数
  • 0xc9指令未实现

leave 指令

填表:

/* 0xc8 */    EMPTY, EX(leave), EMPTY, EMPTY,

填写执行函数:

make_EHelper(leave) {rtl_mv(&cpu.esp, &cpu.ebp);rtl_pop(&cpu.ebp);print_asm("leave");
}

src/cpu/exec/all-instr.h加上函数声明,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=add-longlong run

  • 成功执行leave指令

inc 指令

汇编代码:

100144:   ff 45 e0                incl   -0x20(%ebp)

查表发现是指令组指令Indirct Grp5,到第五个指令组找到inc在第一个,顺便补全第二个dec指令,补全操作码表:

  /* 0xff */make_group(gp5,EX(inc), EX(dec), EMPTY, EMPTY,EMPTY, EMPTY, EX(push), EMPTY)

同时在src/cpu/exec/arith.c中补全dec函数,可以仿照inc函数:

make_EHelper(dec) {rtl_subi(&t3, &id_dest->val, 1);operand_write(id_dest, &t3);// 更新ZF,SF标志位rtl_update_ZFSF(&t3, id_dest->width); // rtl_update_ZFSF函数内部临时变量是t0,所以不能用t0传参,否则更新SF会出错,因为更新ZF时,t0会变// 更新OF标志位rtl_xor(&t2, &id_dest->val, &t3);rtl_msb(&t2, &t2, id_dest->width);rtl_set_OF(&t2);print_asm_template1(dec);
}

src/cpu/exec/all-instr.h加上函数声明,保存后运行runall.sh脚本,add-longlong测试通过。编译bit.c程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=bit run

查看汇编代码:

cat ~/ics2022/nexus-am/tests/cputest/build/bit-x86-nemu.txt

运行后:

0x6a指令未完成,查看指令表,发现是PUSH指令。

push Ib 指令

因为是PUSH Ib,所以填写操作码表,0x68也是这个指令,一起补上:

/* 0x68 */    IDEX(push_SI, push), EMPTY, IDEXW(push_SI, push, 1), EMPTY,

这里不能用IDEX(I, push),译码函数应该是有符号数,不然后面coremark跑分测试会报错,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=bit run

成功执行push指令

sar shl shr 指令

0xc1指令通过查表发现是Shift Grp2,查看汇编代码,发现是sar指令。在第二个指令组,sar是第八个,shl,shr在第二个指令组的第六七个,填写操作码表:

/* 0xc0, 0xc1, 0xd0, 0xd1, 0xd2, 0xd3 */
make_group(gp2,EMPTY, EMPTY, EMPTY, EMPTY,EMPTY, EX(shl), EX(shr), EX(sar))

在/src/cpu/exec/logic.c中,补全sar执行代码:

make_EHelper(sar) {rtl_sar(&t1, &id_dest->val, &id_src->val);operand_write(id_dest, &t1);rtl_update_ZFSF(&t1,id_dest->width);// unnecessary to update CF and OF in NEMUprint_asm_template2(sar);
}

在/src/cpu/exec/logic.c,仿照sar,补全 shl 执行函数:

make_EHelper(shl) {rtl_shl(&t1, &id_dest->val, &id_src->val);operand_write(id_dest, &t1);rtl_update_ZFSF(&t1, id_dest->width);// unnecessary to update CF and OF in NEMUprint_asm_template2(shl);
}

补全执行函数,可以仿照shl函数:

make_EHelper(shr) {rtl_shr(&t1, &id_dest->val, &id_src->val);operand_write(id_dest, &t1);rtl_update_ZFSF(&t1, id_dest->width);// unnecessary to update CF and OF in NEMUprint_asm_template2(shr);
}

src/cpu/exec/all-instr.h加上函数声明,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=bit run

成功执行shl指令。

cltd 指令

编译goldbach.c文件:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=goldbach run

0x99指令没有实现,查看汇编代码,发现是 cltd 指令。

填写操作码表:

  /* 0x98 */    EX(cwtl), EX(cltd), EMPTY, EMPTY,

完成执行函数:

make_EHelper(cltd) {if (decoding.is_operand_size_16) {rtl_lr_w(&t0, R_AX); // 取出 AX 放到t0里rtl_sext(&t0, &t0, 2); // 字扩展rtl_sari(&t0, &t0, 16); // 右移16位rtl_sr_w(R_DX, &t0); // 把高16位放到DX里} else {rtl_lr_l(&t0, R_EAX);rtl_sari(&t0, &t0, 31);rtl_sr_l(R_EDX, &t0);}print_asm(decoding.is_operand_size_16 ? "cwtl" : "cltd");
}make_EHelper(cwtl) {if (decoding.is_operand_size_16) {rtl_lr_b(&t0, R_AL); // 取出 AL 放到t0里rtl_sext(&t0, &t0, 1);rtl_sr_w(R_AX, &t0); // 不能使用 reg_w(R_AX) = t0; 否则coremark跑分时打开diff-test会出问题}else {rtl_lr_w(&t0, R_AX); // 取出 AX 放到t0里rtl_sext(&t0, &t0, 2);rtl_sr_l(R_EAX, &t0); // 不能使用 reg_l(R_EAX) = t0; 否则coremark跑分时打开diff-test会出问题}print_asm(decoding.is_operand_size_16 ? "cbtw" : "cwtl");
}

src/cpu/exec/all-instr.h加上函数声明,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=goldbach run

not idiv div mul 指令

查表可得第三个指令组的第三条指令是NOT,mul div idiv在第三个指令组的第5,7,8个指令,补全操作码表:

 /* 0xf6, 0xf7 */
make_group(gp3,EMPTY, EMPTY, EX(not), EMPTY,EX(mul), EMPTY, EX(div), EX(idiv))
  • idiv div mul 已经实现,直接填表

在/src/cpu/exec/logic.c补全执行函数:

make_EHelper(not) {rtl_mv(&t0, &id_dest->val);rtl_not(&t0);operand_write(id_dest, &t0);print_asm_template1(not);
}

src/cpu/exec/all-instr.h加上函数声明,保存后再次运行测试程序:

cd ~/ics2022/nemu/ && ./runall.sh

goldbach.c通过测试。

inc 指令

编译bubble-sort.c程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=bubble-sort run

inc的执行函数已经在src/cpu/exec/arith.c里实现,直接填表,顺便把dec也给填上:

/* 0x40 */    IDEX(r, inc), IDEX(r, inc), IDEX(r, inc), IDEX(r, inc),
/* 0x44 */    IDEX(r, inc), IDEX(r, inc), IDEX(r, inc), IDEX(r, inc),
/* 0x48 */    IDEX(r, dec), IDEX(r, dec), IDEX(r, dec), IDEX(r, dec),
/* 0x4c */    IDEX(r, dec), IDEX(r, dec), IDEX(r, dec), IDEX(r, dec),

src/cpu/exec/all-instr.h加上函数声明,保存后再次运行测试程序:

cd ~/ics2022/nemu/ && ./runall.sh

bubble-sort.c运行成功。

imul 指令

编译fact.c

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=fact run

发现是个双字节操作码,0xaf是imul指令,因为有两个操作数,所以用imul2,补全操作码表:

 /* 0xac */    EMPTY, EMPTY, EMPTY, IDEX(E2G, imul2),

src/cpu/exec/all-instr.h加上函数声明,保存后再次运行测试程序:

cd ~/ics2022/nemu/ && ./runall.sh

fact.c通过测试。

MOVSX 指令

编译hello-str.c程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=hello-str run

是一个双字节操作码:0xbe 是 MOVSX 指令。补全操作码表:

/* 0xbc */    EMPTY, EMPTY, IDEXW(E2G, movsx, 1), IDEXW(E2G, movsx, 2),

E2Gmov_E2G都可以,执行函数在src/cpu/exec/data-mov.c里面已经完成,在src/cpu/exec/all-instr.h加上函数声明,保存后编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=hello-str run

jmp_rm call_rm指令

查看汇编代码:

10062f:       ff 24 95 a8 08 10 00    jmp    *0x1008a8(,%edx,4)

src/cpu/exec/control.c中找到jmp_rm,他的执行函数最后一句打印汇编出现了*号,所以可以确定这条jmp应该用jmp_rm执行,补全操作码表:

  /* 0xff */make_group(gp5,EX(inc), EX(dec), EX(call_rm), EMPTY,EX(jmp_rm), EMPTY, EX(push), EMPTY)

补全call_rm的执行函数:

make_EHelper(call_rm) {decoding.is_jmp = 1;decoding.jmp_eip = id_dest->val;rtl_push(eip);print_asm("call *%s", id_dest->str);
}

src/cpu/exec/all-instr.h加上函数声明,保存后再次运行测试程序:

cd ~/ics2022/nemu/ && ./runall.sh

成功运行hello-str。

imul1 指令

再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=recursion run

发现imul指令没有完成,补全操作码表:

  /* 0xf6, 0xf7 */
make_group(gp3,EMPTY, EMPTY, EX(not), EMPTY,EX(mul), EX(imul1), EX(div), EX(idiv))

src/cpu/exec/all-instr.h加上函数声明,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=recursion run

通过⼀键回归测试(5 分)

IN/OUT 指令(10 分)

src/cpu/exec/system.c里补全指令:

make_EHelper(in) {t1 = pio_read(id_src->val, id_dest->width);operand_write(id_dest, &t1);print_asm_template2(in);#ifdef DIFF_TESTdiff_test_skip_qemu();
#endif
}make_EHelper(out) {pio_write(id_dest->val, id_dest->width, id_src->val);print_asm_template2(out);#ifdef DIFF_TESTdiff_test_skip_qemu();
#endif
}

补全操作码表:

 /* 0xe4 */ IDEXW(in_I2a, in, 1), IDEX(in_I2a, in), IDEXW(out_a2I, out, 1), IDEX(out_a2I, out),/* 0xec */ IDEXW(in_dx2a, in, 1), IDEX(in_dx2a, in), IDEXW(out_a2dx, out, 1), IDEX(out_a2dx, out),

src/cpu/exec/all-instr.h加上函数声明,在 nexus-am/am/arch/x86-nemu/src/trm.c 中定义宏 HAS_SERIAL,保存后编译hello程序:

clear && cd ~/ics2022/nexus-am/apps/hello && make clean && make run

成功输出10行Hello World!

实现时钟设备(10 分)

实现 _uptime() 函数

nexus-am/am/arch/x86-nemu/src/ioe.c里补全_uptime()函数:

unsigned long _uptime() {return inl(RTC_PORT) - boot_time;
}

src/cpu/exec/all-instr.h加上函数声明,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/tests/timetest && make clean && make run

运⾏跑分项⽬(10 分)

注释掉 nemu/include/common.h 中的 DEBUGDIFF_TEST宏。修改src/monitor/cpu-exec.c

void cpu_exec(uint64_t n) {if (nemu_state == NEMU_END) {printf("Program execution has ended. To restart the program, exit NEMU and run again.\n");return;}nemu_state = NEMU_RUNNING;bool print_flag = n < MAX_INSTR_TO_PRINT;for (; n > 0; n --) {
+ #ifdef DEBUGuint32_t old_eip = cpu.eip;
+ #endif/* Execute one instruction, including instruction fetch,* instruction decode, and the actual execution. */exec_wrapper(print_flag);#ifdef DEBUG/* TODO: check watchpoints here. */WP* hit = scan_watchpoint(old_eip);if (hit) nemu_state = NEMU_STOP;#endif#ifdef HAS_IOEextern void device_update();device_update();
#endifif (nemu_state != NEMU_RUNNING) { return; }}if (nemu_state == NEMU_RUNNING) { nemu_state = NEMU_STOP; }
}

因为把DEBUG关了,old_eip就不会被用到,编译会出错。

运行dhrystone

clear && cd ~/ics2022/nexus-am/apps/dhrystone && make clean && make run

运行coremark

clear && cd ~/ics2022/nexus-am/apps/coremark && make clean && make run

发现neg指令没有实现,src/cpu/exec/arith.c执行函数:

make_EHelper(neg) {if(!id_dest->val) // 操作数为零rtl_set_CF(&tzero);else{t0 = 1;rtl_set_CF(&t0);}t0= -id_dest->val; // 取反operand_write(id_dest, &t0);// 更新ZF,SF标志位rtl_update_ZFSF(&t3, id_dest->width);// 更新OF标志位rtl_xor(&t1, &id_dest->val, &id_src->val);rtl_xor(&t2, &id_dest->val, &t3);rtl_and(&t0, &t1, &t2);rtl_msb(&t0, &t0, id_dest->width);rtl_set_OF(&t0);print_asm_template1(neg);
}

补全操作码表:

  /* 0xf6, 0xf7 */
make_group(gp3,EMPTY, EMPTY, EX(not), EX(neg),EX(mul), EX(imul1), EX(div), EX(idiv))

src/cpu/exec/control.c中写入iret指令:

make_EHelper(reti) {rtl_pop(&decoding.jmp_eip);decoding.is_jmp = 1;cpu.esp += id_dest->val;print_asm("ret");
}

填写操作码表:

/* 0xc0 */    IDEXW(gp2_Ib2E, gp2, 1), IDEX(gp2_Ib2E, gp2), IDEXW(I, reti, 2), EX(ret),

src/cpu/exec/all-instr.h加上函数声明,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/apps/coremark && make clean && make run

运行microbench

补全rol指令执行函数:

make_EHelper(rol) {for(t0 = 0;t0 < id_src->val; t0++){rtl_msb(&t1,&id_dest->val,id_dest->width);rtl_shli(&id_dest->val,&id_dest->val,1);rtl_xor(&id_dest->val,&id_dest->val,&t1);}rtl_set_CF(&t1);operand_write(id_dest,&id_dest->val);print_asm_template2(rol);
}

填写操作码表:

  /* 0xc0, 0xc1, 0xd0, 0xd1, 0xd2, 0xd3 */make_group(gp2,EX(rol), EMPTY, EMPTY, EMPTY,EX(shl), EX(shr), EMPTY, EX(sar))

src/cpu/exec/all-instr.h加上函数声明,保存后再次编译程序:

clear && cd ~/ics2022/nexus-am/apps/microbench && make clean && make run

实现键盘设备(10 分)

在nexus-am/am/arch/x86-nemu/src/ioe.c实现 _read_key() 函数:

#define I8042_DATA_PORT 0x60
#define I8042_STATUS_PORT 0x64
int _read_key() {if (inb(I8042_STATUS_PORT) == 1) {return inl(I8042_DATA_PORT);}return _KEY_NONE;
}

添加内存映射 I/O (10 分)

修改nemu/src/memory/memory.cpaddr_read()paddr_write()函数.

#include "device/mmio.h"uint32_t paddr_read(paddr_t addr, int len) {int mmio_id = is_mmio(addr);if (mmio_id != -1) {return mmio_read(addr, len, mmio_id);}return pmem_rw(addr, uint32_t) & (~0u >> ((4 - len) << 3));
}void paddr_write(paddr_t addr, int len, uint32_t data) {int mmio_id = is_mmio(addr);if (mmio_id != -1) {mmio_write(addr, len, data, mmio_id);}else {memcpy(guest_to_host(addr), &data, len);}
}

运⾏打字⼩游戏(5 分)

运行命令:

cd ~/ics2022/nexus-am/apps/typing && make clean && make run

References

PA2.2&PA2.3-物联网技术文章-傲云电气网

PA2.2 PA2.3_qq_42093390的博客-CSDN博客

2.3 输入输出

GitHub - qrzbing/ics-pa at pa2

GitHub - hitworld/NUAA_PA

南航 PA2.2PA2.3相关推荐

  1. 南航计算机硬件实验,南航80X86微机原理及接口技术实验指导书.pdf

    80X86 微机原理及接口技术实验 指导书 卓然 编著 2015-3-1 序 错误!未定义书签. 第一章 TD-PIT++实验系统简介 3 1. 概述 3 2. 系统总线电路单元 5 3. 接口实验单 ...

  2. 777后无效 执行chmod_厉害了!南航777机队和南航空姐在人民大会堂接受表彰!

    今天上午 在人民大会堂召开的 全国抗击新冠肺炎疫情表彰大会上 南航飞行总队波音777机队和南航乘务长田静 分别荣获"先进集体"和"先进个人"称号! ▲乘务长田静 ...

  3. 南航里程每年清空吗_航空里程被盗用,多位明星中招!隐私保护真的这么难吗?...

    近日,"站姐盗用吴磊里程积分"的话题登上微博热搜榜. 一位吴磊的粉丝晒出了与航空公司工作人员的聊天视频,从视频中的聊天可以看到一位吴磊的站姐从2017年开始私下将吴磊的飞机里程用来 ...

  4. 空客fctm避免已识别风险_最远可航行15000公里,南航首架空客A350飞机首航,将先飞广州-上海航线再飞国际...

    今天上午,南航首架空客A350飞机(注册号为B-308T)从广州白云机场起飞,执行CZ3523(广州-上海虹桥)航班,于09:56分抵达上海虹桥机场,完成首飞.中午12:15分,这架A350从虹桥机场 ...

  5. 江苏机器人竞赛南航_中国青少年机器人竞赛

    在金珠小学是广东省首批"科普中国e站"建设单位,学校发挥青少年科学教育特色,为新时代科学传播插上"e翅膀",做到线上与线下结合,把握科技发展脉动,紧盯科技创新趋 ...

  6. 新东方mti百科知识pdf_20南航翻硕mti初试417上岸经验贴

    南京航空航天大学mti初试417分排名第一: ‌基础英语88: 1,外刊阅读:从2月到6月开始一直打卡外刊app,友邻优课,流利阅读等 2,背单词:扇贝单词app,新东方专八单词绿皮书,华研专八单词等 ...

  7. c语言正则表达式_CS143:编译原理|PA2:正则表达式和词法分析

    本文使用 Zhihu On VSCode 创作并发布 这是本人实现斯坦福CS143变编程作业的笔记,对应第二次作业PA2.有关文章目录.环境搭建和一些说明请看第一篇:CS143:编译原理 | 环境搭建 ...

  8. 江苏机器人竞赛南航_挑战不止 热血不息!1000余名青少年决战江苏省机器人普及大赛!...

    11月2-3日,200多名绿超人在南京航空航天大学集结,迎战第二十六届江苏省青少年科技模型大赛南京市选拔赛! 本次比赛由省青少年科技教育协会牵头组织,项目繁多.共有1000多名参赛选手参赛,是江苏省下 ...

  9. 南航里程每年清空吗_南航里程即将大幅贬值!此期限前使用仍能保值

    银行积分如何变现更划算?公认的答案是兑换航司里程.现在,南航里程即将缩水!真是四面楚歌啊-- 最新消息,南航即将在7月20日推出铂金卡.终身铂金卡,权益不错,但随之而来的也有南航里程大缩水. 先说升级 ...

最新文章

  1. You must reset your password using ALTER USER statement before executing thi
  2. 区块链中的智能合约是什么?
  3. 20. matlab 中的gtext 函数
  4. 机器人懂点「常识」后,找东西方便多了:CMU打造新型语义导航AI机器人
  5. python读取excel文件-Python 读写excel文件
  6. 记与公司内网微博的谈话
  7. 20201202 《计算感知》武老师 第2节课 笔记
  8. mac word维吾尔文字体_字加软件更新啦!万款字体一键激活!
  9. css预编译其器,CSS预处理器之——Less
  10. 浏览器angent分析工具
  11. Arbitrage——判断正环Bellman-Ford/SPFA
  12. mybatis update 返回值
  13. 北斗导航 | 基于改进RANSAC算法的BDS接收机自主完好性监测算法研究
  14. jieba 同义词_jieba分词详解
  15. mysql数据库密码怎么修改_MySQL数据库密码如何修改?
  16. 根据CTP接口计算现手、增仓、开平、对手盘 (1)
  17. 微信V3版本支付下单、查询支付订单状态、订单退款接入正式项目中并引入策略模式实操
  18. 深入AXI4总线- [一] 握手机制
  19. 【如何使用高级语言在机器语言层面提高程序运行效率】
  20. Python笔记:利用pygame模块实现三原色颜色滚动条效果

热门文章

  1. 2006世界杯32强人体彩绘队服样式(法国)
  2. N天学习一个Linux命令之top
  3. java用while和for循环分别计算100以内奇数的和
  4. IOS OpenGL ES GPUImage 图像普瑞维特(Prewitt)边缘检测 GPUImagePrewittEdgeDetectionFilter
  5. 学习记录:STM32F103 时钟系统概述工作原理
  6. 实用---生命游戏 Java
  7. VC10如何在Release模式下调试代码
  8. php易语言互交_易语言php调用源码
  9. php保留一位小数_php保留小数点后两位的几种方法
  10. loadrunner java脚本编写