您的位置:首页 > 科技 > 能源 > 南大-ICS2021 PA1~PA2.2 学习笔记记录

南大-ICS2021 PA1~PA2.2 学习笔记记录

2024/11/17 4:32:26 来源:https://blog.csdn.net/xiao3_tai/article/details/141388685  浏览:    关键词:南大-ICS2021 PA1~PA2.2 学习笔记记录

文章目录

  • 代码github网址
  • ICS2021其他博客
  • 基础设施: 简易调试器
  • 表达式求值
    • 词法分析
    • 递归求值
    • 如何测试自己的代码
  • 监视点的实现
    • 扩展表达式求值的功能
    • 实现监视点
  • 阅读源码 2
    • 译码
    • 执行
    • 用RTL表示指令行为
    • 实现常用的库函数
    • 实现常用的库函数

代码github网址

https://github.com/xiao-tai/ics2021

ICS2021其他博客

南大-ICS2021 PA2.3 学习笔记&记录
南大-ICS2021 PA3.1 学习笔记&记录
南大ICS2021–实现库函数vsnprintf

基础设施: 简易调试器

这些都还比较简单实现, 具体可以查看nemu/src/sdb/sdb.c中的

  • cmd_c: 启动执行

  • cmd_q: 退出NEMU

  • cmd_si: 单步执行

  • cmd_x: 打印内存

  • cmd_p: 进行表达式求值, 具体实现看下节

表达式求值

主要文件为src/monitor/sdb/expr.c,具体可以查看我的github

词法分析

对于一下表达式:

"0x80100000+   ($a0 +5)*4 - *(  $t1 + 8) + number"

我们需要将各个单元(也就是token)给识别出来, 使用正则表达式可以方便的匹配出一些复杂的pattern, 代码框架中的init_regex()函数负责将定义的正则表达式进行验证, 函数在此处报错通常是因为使用了非法的正则表达式

在这里插入图片描述
本人所用到的正则表达式如下图

在这里插入图片描述

然后, 给定一个表达式, 通过make_token()来识别token类型, 将识别结果保存在一个tokens[i]中, 将对应的字符串保存在str[32]属性中, 类型保存在type属性中, 以上述表达式为例,

tokens[0].str = 0x80100000, tokens[0].type = TK_HEX_NUM, 以此类推, 这样一个表达式所有的taoken都识别完毕了.

递归求值

这里就是涉及到算法的知识了, 需要用到分治法, 这类方法在排序上也有一个经典的应用–快速排序算法. 表达式求值使用上, 我们需要先对短表达式求值, 再对长表达式求值, 所以要使用递归. 这里需要考虑括号的问题和运算符优先级的问题.

eval(p, q) {if (p > q) {/* Bad expression */}else if (p == q) {/* Single token.* For now this token should be a number.* Return the value of the number.*/}else if (check_parentheses(p, q) == true) {/* The expression is surrounded by a matched pair of parentheses.* If that is the case, just throw away the parentheses.*/return eval(p + 1, q - 1);}else {op = the position of 主运算符 in the token expression;val1 = eval(p, op - 1);val2 = eval(op + 1, q);switch (op_type) {case '+': return val1 + val2;case '-': /* ... */case '*': /* ... */case '/': /* ... */default: assert(0);}}
}

对于一个长表达式, 需要先找出主运算符, 本人计算主运算符逻辑是

  • 遍历p和q之间所有的运算符

  • 优先级高的运算符放到后面进行if判断, 因为主运算符是最后一步才进行运算的

// 获取主运算符的位置
int get_position(int p, int q) {int pos = 0;int num = 0;bool plus_or_sub = false;bool mul_or_div = false;bool minus = false;bool deref = false;for(int i = p; i <= q; i++) {if(tokens[i].type == '(')num++;if(tokens[i].type == ')')num--;if(num != 0)continue;if(tokens[i].type == TK_EQ) {return i;}// The lower the priority, the higher the judge will beif(tokens[i].type == '+' || tokens[i].type == '-') {pos = i;plus_or_sub = true;continue;}        if((tokens[i].type == '*' || tokens[i].type == '/') && !plus_or_sub) {pos = i;mul_or_div = true;continue;}if(tokens[i].type == TK_MINUS && !plus_or_sub && !mul_or_div && !minus) {pos = i;minus = true;}if(tokens[i].type == TK_DEREF && !plus_or_sub && !mul_or_div && !deref) {pos = i;deref = true;}}return pos;
}

如何测试自己的代码

写一个随机生成表达式的程序, 要求合法, 其实就是将生成的表达式, 例如1 + 2 + 3传入一个定义好的文件中

#include <stdio.h>int main() {unsigned result = 1 + 2 + 3;printf("%u\n", result);
}

监视点的实现

扩展表达式求值的功能

在递归求解表达式前, 要区分*是乘法运算符, 还是解引用, 所以在进行递归前, 先做一次判断, 以区分负号和减号, 乘号和解引用运算符:

for (int i = 0; i < nr_token; i++) {if(tokens[i].type == '-') {if(i == 0 || (tokens[i-1].type != ')' && tokens[i-1].type != TK_NUM))tokens[i].type = TK_MINUS;}if(tokens[i].type == '*') {if(i == 0 || (tokens[i-1].type != ')' && tokens[i-1].type != TK_NUM))tokens[i].type = TK_DEREF;}
}

实现监视点

使用两个链表维护一个监视点池, head负责维护使用中的监视点结构, free_负责维护空闲的监视点结构. 监视点的机构体如下:

typedef struct watchpoint {int NO;struct watchpoint *next;// 额外添加两个成员, 用于后续的扫描char exp[32]; //表达式uint32_t res;
} WP;

这时还需要增加监视点和删除监视点这两个函数, 同时还需要有扫描所有监视点的函数:

  • 增加监视点(new_wp): 申请一个空闲监视点, 并将表达式记录下来

  • 删除监视点(free_wp(int no)): 通过监视点编号删除监视点, 将其表达式设置为’\0’, 即代表该监视点不参与监视

  • 扫描监视点(check_all_wp()): 每当cpu_exec()执行一条指令时, 都会调用一次trace_and_difftest(), 所以在其中扫描一次所有监视点, 对其表达式进行求值, 如果结果发生变化, 就打印出来, 并将nemu_state.state变量设置为NEMU_STOP

具体见src/monitor/sdb/watchpoint.c

阅读源码 2

译码

译码上, 框架代码进行了多层抽象.

取指令的时候会把指令记录到s->isa.instr.val, 首先匹配opecode字段, 再匹配func3字段, 再匹配func7字段, 这样isa_fetch_decode()会返回唯一的idx, 用于表示执行的指令.

但是我们还不知道操作对象(比如立即数是多少, 使用哪个寄存器), 使用译码辅助函数

// 宏定义解开的样子
def_DHelper(name) = void decode_name(Decode *s)

每个译码辅助函数负责进行一种类型的操作数译码, 把指令中的操作数信息分别记录在译码信息sdest成员, src1成员和src2成员中, 它们分别代表目的操作数和两个源操作数. nemu/include/cpu/decode.h中还定义了三个宏id_dest, id_src1id_src2, 用于方便地访问它们.

同时为了更好的实现操作数译码和指令译码的解耦, 使用译码操作数辅助函数

def_DopHelper(name) = void decode_op_name(Decode *s, Operand *op, word_t val, bool flag)
  • decode_op_i: 通过译码的指令获得立即数

  • decode_op_r: 通过译码的指令获得寄存器

执行

fetch_decode()中得到的执行辅助函数记录到s->EHelper

在这里插入图片描述

执行辅助函数统一通过宏def_EHelper, 具体通过RTL指令来进行真正的执行

def_EHelper(name) = static inline void exec_name(Decode *s) 

每个执行辅助函数都需要有一个标识该指令的ID以及一个表格辅助函数与之相对应, 这一点是通过一系列宏定义来实现的. 在nemu/src/isa/$ISA/include/isa-all-instr.h中定义用于表示指令列表的宏INSTR_LIST, 它定义了NEMU支持的所有指令.

用RTL表示指令行为

在NEMU中, RTL寄存器只有以下这些

  • 不同ISA的通用寄存器(在nemu/src/isa/$ISA/include/isa-def.h中定义)
  • 临时寄存器s0, s1, s2t0(在nemu/include/rtl/rtl.h中定义)
  • 零寄存器rz(在nemu/include/rtl/rtl.h中定义), 它的值总是0

进入nemu/build/obj-riscv32-nemu-interpreter/src/cpu/cpu-exec.i查看具体的执行情况, g_exec_table如下:

static const void *g_exec_table[TOTAL_INSTR] = {[EXEC_ID_lui] = exec_lui,[EXEC_ID_lb] = exec_lb,[EXEC_ID_lbu] = exec_lbu,[EXEC_ID_lh] = exec_lh,[EXEC_ID_lhu] = exec_lhu,[EXEC_ID_lw] = exec_lw,[EXEC_ID_sw] = exec_sw,[EXEC_ID_sh] = exec_sh,[EXEC_ID_sb] = exec_sb,//.....
}

各个函数的具体实现位于src/isa/riscv32/instr/compute.h中, 通过以下指令查找得到:

xiaoxTai:nemu$ grep -r "rtl_add" ../src/isa/riscv32/instr/compute.h:  rtl_addi(s, ddest, dsrc1, id_src2->imm);
./src/isa/riscv32/instr/compute.h:  rtl_add(s, ddest, dsrc1, dsrc2);

在这里插入图片描述

所以实现一个新指令的步骤如下:

  1. nemu/src/isa/riscv32/instr/decode.c中添加正确的模式匹配规则
  2. 用RTL实现正确的执行辅助函数, 在./src/isa/riscv32/instr/compute.h或者instr文件夹下其他的头文件
  3. nemu/src/isa/riscv32/include/isa-all-instr.h中把指令添加到INSTR_LIST
  4. 必要时在nemu/src/isa/riscv32/include/isa-exec.h添加相应的头文件, 言外之意就是第二步写在哪个头文件下, 就把那个头文件, 加到这个isa-exec.h

在这里插入图片描述

接下来就是通过运行程序, 检查还有哪些指令没有实现, 安装好交叉编译环境, 通过执行

make ARCH=riscv32-nemu ALL=dummy run

运行结果打印的信息会提示你还有什么指令没有实现, 当然要通过在am-kernels/tests/cpu-tests/build/dummy-$ISA-nemu.txt进行搜索

实现常用的库函数

相关库函数的具体实现, 也是面试中出现频率较高的问题了, 主要在abstract-machine/klib/src/

  • strcpy, memset等函数在string.c

  • sprintf()stdio.c下, 这个函数实现的讲解见我的另一篇博客

  • atoi, malloc在stdlib.c下
    果打印的信息会提示你还有什么指令没有实现, 当然要通过在am-kernels/tests/cpu-tests/build/dummy-$ISA-nemu.txt进行搜索

实现常用的库函数

相关库函数的具体实现, 也是面试中出现频率较高的问题了, 主要在abstract-machine/klib/src/

  • strcpy, memset等函数在string.c

  • sprintf()stdio.c下, 这个函数实现的讲解见我的另一篇博客南大ICS2021–实现库函数vsnprintf

  • atoi, malloc在stdlib.c下

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com