一生一芯日记

记录下一生一芯的学习过程

理解mainargs

image-20250130205522617在nemu中,mainargs是通过am/src/platform/nemu/trm.c中的_trm_init()函数传递给main()函数的(在这里调用了main()函数)。在trm.c中定义了mainargs数组,并且在scripts/platform/nemu.mk中通过CFLAGS += -DMAINARGS="$(mainargs)"宏定义了mainargs是什么。
在npc中,mainargs是通过在am/src/native/platform.c中的init_platform()传递给main函数的(在这里调用了main()函数)。这里通过getenv(“mainargs”)这个函数获取到了mainargs。

实现printf

image-20250130205536885为了复用之前实现sprintf的代码,把大部分sprintf的功能实现放到了vsprintf中,在sprintf中建立好va_list传入就重新实现了sprintf,printf同样,建好va_list传入sprintf就行。
数字转字符串的函数有点问题,没有考虑INT_MIN(我直接把数字加个符号拿去%10了),重写了一遍这个函数,单独考虑了INT_MIN。

实现IOE

image-20250130210554582nemu中每次访问设备,都会调用每个设备的回调函数,去更新时间的寄存器。在rtc的测试中,io_read()会访问指定的设备寄存器,匹配到AM_TIMER_UPTIME寄存器后,会调用__am_timer_uptime()去访问内存读取时间。

需要先访问高位内存,nemu中访问高位内存后才去更新时间的内存地址。

看看NEMU跑多快

跑的时候竟然发现nemu的slt和lb指令没有实现

image-20250130211011101

image-20250130231420800image-20250131152413121image-20250131152446896image-20250131152611405

实现malloc()

malloc()从heap中分配内存,heap的起始地址是extern char _heap_start指定的,_heap_start的值是在链接脚本中给的。

根据RTFM,malloc()要求返回的内存地址必须适合于在该平台上任意数据类型的对齐要求;所以我采取了8字节对齐。

native与klib

image-20250201183444110

在native.mk中g++ -pie -o $(IMAGE) -Wl,--whole-archive $(LINKAGE) -Wl,-no-whole-archive $(LDFLAGS_CXX) -lSDL2 -ldl,SDL2库是被动态链接上的,所以当程序运行的时候,SDL2会被链接上glibc的函数,而不是klib中的。

dtrace

跟mtrace差不多

键盘

nemu中把SDL的键盘码转换成了NEMU自己的键盘码,看懂代码实现起来还是比较简单的。

image-20250206021058707

键盘分成了通码和断码,只要检测到了通码,没有检测到断码,就说明这个键一直被按下,这时候再检测到另一个按键的通码,另一个按键的状态也成了按下,这样就实现了检测多个按键同时被按下。

VGA

实现IOE(3)

就是完成am的gpu.cvoid __am_gpu_config(AM_GPU_CONFIG_T *cfg)和nemu的vga.cvga_update_screen()处的代码,然后复制讲义提供的__am_gpu_init()的代码,获取w和h变量的值,让gpu有个初始的图像。获取w和h值的方法就是io_read()前面实现的gpu配置寄存器。最后效果如下。

image-20250207013553941

am-test的video测试中,redraw()函数刚开始看懵了,它的工作原理是把屏幕分成了N*N的块,每一块的像素是w*h。随后的三层循环就是给块中的每个像素填充同一个颜色。

void redraw() {
int w = io_read(AM_GPU_CONFIG).width / N;
int h = io_read(AM_GPU_CONFIG).height / N;
int block_size = w * h;
assert((uint32_t)block_size <= LENGTH(color_buf));

int x, y, k;
for (y = 0; y < N; y ++) {
for (x = 0; x < N; x ++) {
for (k = 0; k < block_size; k ++) {
color_buf[k] = canvas[y][x];
}
io_write(AM_GPU_FBDRAW, x * w, y * h, color_buf, w, h, false);
}
}
io_write(AM_GPU_FBDRAW, 0, 0, NULL, 0, 0, true);
}

实现IOE(4)

AM_DEVREG(11, GPU_FBDRAW,   WR, int x, y; void *pixels; int w, h; bool sync);

GPU_FBDRAW是以屏幕(x,y)为左上角,绘制一个w*h的矩形。

玩上马里奥了!!!

image-20250207212627627

为NPC实现时钟和串口

讲义给的代码中,实现NPC的内存是使用组合逻辑的,所有Verilator仿真的时候,内存有时会在本周期写入(实际应该是下个周期开始时才写入数据),这导致了串口会重复输出两次,需要在仿真代码中处理一下重复的情况。

时钟的实现照搬nemu的,没什么问题。

最后跑马里奥,实在是太卡了,三十多秒才更新1帧(可能是我仿真的代码有问题?)

截图 2025-02-15 21-20-23