“一生一芯”计划 预学习阶段

贯穿计算机专业本科课程(数字逻辑、计算机组成原理、计算机体系结构、操作系统、计算机系统设计)很厉害的项目。

难度较大,希望自己能够坚持下去。

阅读两篇关于如何提问的文章并编写一篇不少于800字的读后感(2022.7.5)

已完成阅读,读后感见 (隐私手动打码)-提问的智慧读后感.pdf。

Linux 系统安装 (PA0) 和基本使用(2022.7.5)

安装 Linux 操作系统

暂时使用虚拟机 VMware 中安装 64 位的 Ubuntu 21.04。

根据 PA0 讲义 安装好这些工具

1
2
3
4
5
6
7
8
apt-get install build-essential    # build-essential packages, include binary utilities, gcc, make, and so on
apt-get install man # on-line reference manual
apt-get install gcc-doc # on-line reference manual for gcc
apt-get install gdb # GNU debugger
apt-get install git # revision control system
apt-get install libreadline-dev # a library used later
apt-get install libsdl2-dev # a library used later
apt-get install llvm llvm-dev # llvm project, which contains libraries used later

不想用 Vim,用开源版本的 VSCodium 多好啊(

Write a “Hello World” program under GNU/Linux

先随便新建并进入一个目录中

创建一个源代码文件:touch helloworld.cpp

代码截图如下:

直接在命令行编译并运行如下:

Write a Makefile to compile the “Hello World” program

小菜一碟。

Makefile 截图如下:

使用 make 命令编译并运行:

Learn to use GDB

就是一个调试器,之后在 PA1 中也会完成一个类似的。

获取“一生一芯”框架代码

在自己创好的目录下执行git clone -b ysyx2204 git@github.com:OSCPU/ysyx-workbench.git

完成好 git config

测试环境变量 $NEMU_HOME$AM_HOME,发现一切正常。

Compiling and Running NEMU

在得到 NEMU 代码后,运行 make menuconfig,报错。发现缺少了 bisonflex 这两个软件。

再用 apt 命令安装上即可。

学习 Linux 基本使用

复习 C 语言知识

搭建 Verilator 仿真环境(2022.7.5)

首先设置好环境变量 $NPC_HOME

STFW+RTFM

认识 Verilator

查找官方文档,简单概括一下。

Verilator 是高性能 Verilog HDL 模拟器,允许用户通过 C++/SystemC 对 RTL 进行验证。

使用时会生成一系列 C++/SystemC 文件,这些文件由 C++ 编译器进行编译,并通过最终生成的可执行文件执行设计模拟。

安装 Verilator

具体安装教程可以参考官方安装教程,通过 git 获取源码安装。

切换到要求的 4.210 版本分支

再根据教程使用命令安装,最终安装成功。

运行示例

手册中包含的 C++ 网址

新建一个文件 our.v,具体代码如下

1
2
3
module our;
initial begin $display("Hello World"); $finish; end
endmodule

新建一个文件 sim_main.cpp,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
#include "Vour.h"
#include "verilated.h"
int main(int argc, char** argv, char** env) {
VerilatedContext* contextp = new VerilatedContext;
contextp->commandArgs(argc, argv);
Vour* top = new Vour{contextp};
while (!contextp->gotFinish()) { top->eval(); }
delete top;
delete contextp;
return 0;
}
  • top->eval(); 是为了在每个时钟周期获取对应的输出数据
  • contextp 是专门用来设置仿真环境的指针

编译命令为:verilator -Wall --cc --exe --build sim_main.cpp our.v

编译完成后,生成了一个文件夹obj_dir,可以运行其中的可执行文件 Vour

示例:双控开关

根据实验手册的提示,创建并完善代码。

verilog 代码 top.v

1
2
3
4
5
6
7
module top(
input a,
input b,
output f
); assign f = a ^ b;
endmodule

对双控开关进行仿真

根据提示的伪代码修改得到的 C++ 文件 sim_main.cpp

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
#include "verilated.h"
#include "Vtop.h"
#include "stdio.h"
#include <stdlib.h>
#include <time.h>

Vtop *top;//module object
vluint64_t cur_time;//current simulation time

double sc_time_stamp(){
return cur_time;
}

int main(int argc, char** argv){
Verilated::commandArgs(argc, argv);
srand((unsigned)time(NULL));
int a,b;
top = new Vtop;
while(!Verilated::gotFinish()){
a=rand()&1;
b=rand()&1;
top->a=a;
top->b=b;
top->eval();
assert(top->f == a^b);
printf("a=%d, b=%d, f=%d\n", a, b, top->f);
cur_time++;
}
top->final();
delete top;
return 0;
}

运行编译命令 verilator -Wall --cc --exe --build sim_main.cpp top.v 生成 obj_dir 文件夹。运行 /obj_dir/Vtop 运行可执行程序,发现能正确输出。

理解 RTL 仿真的行为

Verilator 编译出的 C++ 代码在 /obj_dir/Vtop.cpp/obj_dir/Vtop.h 中。

在头文件中可以看到,模块 top 的输入和输出被绑定好了。执行时调用 eval 函数。

打印波形并查看

按照手册提示安装好 GTKWave。

要输出波形图,还需要对代码进行稍微修改,加入 trace 波形的相关变量和代码。

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
43
44
45
46
#include "verilated_vcd_c.h"//wave vcd
#include "Vtop.h"
#include "stdio.h"
#include <stdlib.h>
#include <time.h>

Vtop *top;//module object
vluint64_t cur_time=0;//current simulation time

double sc_time_stamp(){
return cur_time;
}

void time_inc(){
cur_time++;
}

int main(int argc, char** argv){
Verilated::commandArgs(argc, argv);
Verilated::traceEverOn(true);//export vcd
VerilatedVcdC* vcd = new VerilatedVcdC;//vcd
srand((unsigned)time(NULL));
int a,b;
top = new Vtop;

top->trace(vcd, 0);
vcd->open("wave.vcd");

//sampling 20 times
while(sc_time_stamp()<20 && !Verilated::gotFinish()){
a=rand()&1;
b=rand()&1;
top->a=a;
top->b=b;
top->eval();
//printf("a=%d, b=%d, f=%d\n", a, b, top->f);
//assert(top->f == a^b);
vcd->dump(sc_time_stamp());
time_inc();
}
top->final();
vcd->close();
delete top;
delete vcd;
return 0;
}

生成目标文件夹 obj_dir:verilator -Wno-fatal top.v sim_main.cpp --top-module top --cc --trace --exe。其中 --trace 代表追踪波形以便于后续生成。

编译链接编译出的文件:make -C obj_dir -f Vtop.mk Vtop

运行可执行文件并生成波形图:/obj_dir/Vtop

查看波形图:gtkwave wave.vcd

查看结果如下,经检查,波形正确:

编写 Makefile 进行一键仿真

尝试为 npc/Makefile 编写规则 sim,实现一键仿真,如键入 make sim 即可进行上述仿真。

接入 NVBoard

虚拟 FPGA 板卡项目,可以在 RTL 仿真环境中提供一个虚拟板卡的界面。支持拨码开关、LED灯、VGA 显示等功能。

先根据手册提示获取 NVBoard 代码。

运行 NVBoard 示例

在 example 文件夹中,键入命令 make run 运行示例。发现是一个循环显示数码和循环显示 LED 的板子和一张南京大学校内的照片。

在 NVBoard 上实现双控开关

仿照示例下的 C++ 文件和 Makefile 编写我的 C++ 文件和 Makefile。并编写约束文件分配输入输出引脚。

C++ 文件代码如下:

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
#include "verilated_vcd_c.h"
#include "Vtop.h"
#include "stdio.h"
#include <nvboard.h>
#include <stdlib.h>
#include <time.h>

static TOP_NAME dut;
Vtop *top;//module object

void nvboard_bind_all_pins(Vtop* top);

int main(int argc, char** argv){
Verilated::commandArgs(argc, argv);
Verilated::traceEverOn(true);//export vcd

nvboard_bind_all_pins(&dut);
nvboard_init();
int a,b;
top = new Vtop;
while(true){
dut.eval();
nvboard_update();
}
top->final();
delete top;
nvboard_quit();
return 0;
}

Makefile 文件基本复制样例,并做了一点点修改

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
include $(NVBOARD_HOME)/scripts/nvboard.mk
#生成的可执行文件名称
TOPNAME = top
#在指定文件夹中按扩展名查找所有源文件
VSRC = $(shell find $(abspath /vsrc) -name "*.v")
CSRC = $(shell find $(abspath /csrc) -name "*.c" -or -name "*.cc" -or -name "*.cpp")
#nvbroad自动绑定源文件,注意,如果没有$(BUILD_DIR)目录,将无法正确的生成该文件
SRC_AUTO_BIND = $(abspath $(BUILD_DIR)/auto_bind.cpp)
CSRC += $(SRC_AUTO_BIND)
#nvbroad约束文件
CONSTR = constr/top.nxdc

#构建目录、verilator目标目录、可执行文件存放路径
BUILD_DIR = /build
OBJ_DIR = $(BUILD_DIR)/obj_dir
BIN = $(BUILD_DIR)/$(TOPNAME)

#传递给verilator的参数:以c++形式输出调试信息,并自动依据makefile进行构建
VERIARG = -Wall --cc -MMD --build

#传递给preproject连接器的信息,添加SDL2库信息
LDFLAGS += -lSDL2 -lSDL2_image

#包含文件路径,加前缀-I传递给g++,INC_PATH在nvbroad.mk中定义
INCFLAGS = $(addprefix -I, $(INC_PATH))

#传递给g++的编译参数,包括包含路径和TOP_NAME的定义
CFLAGS += $(INCFLAGS) -DTOP_NAME="\"V$(TOPNAME)\""

#包含nvbroad.mk
include $(NVBOARD_HOME)/scripts/nvboard.mk
#新建构建目录,防止出现无法新建autobind.cpp的错误
$(shell mkdir -p $(BUILD_DIR))

#默认的构建对象
sim: $(CSRC) $(VSRC) $(NVBOARD_ARCHIVE)
#提交当前存储区,提交信息为sim RTL
$(call git_commit, "sim RTL") # DO NOT REMOVE THIS LINE!!!

#运行verilator,传递给其编译参数
#指定top模块名称
#添加所有源文件和nvbroad库
#传递所有preproject连接器参数,加前缀
#传递所有编译器参数,加前缀
#指定生成对象目录,生成可执行文件,输出路径为预先指定的可执行文件存储路径
verilator $(VERIARG) \
-top $(TOPNAME) \
$^ \
$(addprefix -LDFLAGS , $(LDFLAGS)) \
$(addprefix -CFLAGS , $(CFLAGS)) \
--Mdir $(OBJ_DIR) --exe -o $(abspath $(BIN))

clean:
rm -rf $(BUILD_DIR)

#自动绑定文件,由于其被可执行文件依赖,会先生成该文件。调用python执行nvbroad自带的.py文件,输入约束文件,输出自动绑定文件
$(SRC_AUTO_BIND): $(CONSTR)
python3 $(NVBOARD_HOME)/scripts/auto_pin_bind.py $^ $@

实现引脚绑定的约束文件 /constr/top.nxdc,将两个输入绑定在了 0 号和 1 号开关上,输出结果绑定在 0 号 LED 灯上。

1
2
3
4
5
top=top

a SW0
b SW1
f LD0

完成后,在目录下执行 make sim 将代码烧录到 NVBoard 上。运行 /build/top 效果如下:(结果显示正确)

示例:流水灯

流水灯是按照顺序依次亮起和熄灭的一组灯

将流水灯接入 NVBoard

流水灯 Verilog 文件直接照抄讲义上的代码,C++ 文件根据讲义上的伪代码编写如下:

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
#include "verilated.h"
#include "Vtop.h"
#include "stdio.h"
#include <nvboard.h>
#include <stdlib.h>
#include <time.h>

static TOP_NAME dut;
Vtop *top;//module object

void nvboard_bind_all_pins(Vtop* top);

void single_cycle(){
dut.clk=0;
dut.eval();
dut.clk=1;
dut.eval();
}

void reset(int n){
dut.rst=1;
while(n-->0)single_cycle();
dut.rst=0;
}
int main(int argc, char** argv){
VerilatedContext* contextp = new VerilatedContext;
contextp->commandArgs(argc, argv);
top = new Vtop{contextp};

nvboard_bind_all_pins(&dut);
nvboard_init();
reset(10);
while(1){
nvboard_update();
single_cycle();
}
top->final();
delete top;
delete contextp;
nvboard_quit();
return 0;
}

约束文件 top.nxdc 如下:

1
2
3
4
top=top

led (LD15, LD14, LD13, LD12, LD11, LD10, LD9, LD8, LD7, LD6, LD5, LD4, LD3, LD2, LD1, LD0)

完成代码后,在目录下 make sim,编译完成后 /build/top,就能看到灯从右端往左端依次亮起并熄灭。

理解 RTL 仿真的行为

Verilator 的思路是把 rtl 文件当作一个可以执行的“函数”,通过对其抽象为对象来执行各种操作。

数字电路基础实验 2022.7.7~2022.7.12

本部分将借助 NVBoard 完成南京大学的数电与计组实验

你需要完成"CPU数据通路"之前的大部分实验内容, 除了以下例外

  • "在线测试"的内容需要加入相关课程才能完成, 目前可以忽略
  • 计数器和时钟: 由于仿真环境下无法提供精确的时钟, 时钟部分的实验难以准确进行, 因此可作为阅读材料进行了解
  • 寄存器组及存储器: 讲义中建议通过工具相关的IP核实现存储器, 但仿真环境下不存在这样的IP核, 无法开展实验, 因此可作为阅读材料进行了解
  • 关于"CPU数据通路"及其后续内容, "一生一芯"将会有所改动, 因此在预学习阶段无需完成

我完成的具体代码链接在此

由于实验七、八、九没什么思路,暂时战术跳过。

完成 PA1 阶段 1 2022.7.13~2022.7.15

阅读 PA 讲义中的 FAQ(常见问题):略

根据讲义内容,使用 make menuconfig 命令,将 Basic ISA 切换成 riscv64。

进入 nemu 目录下,先 make run,发现报错,根据提示找到代码,将 assert(0) 的地方注释掉,再运行,发现能成功运行。命令 help 显示支持的命令;命令 c 表示继续执行到程序结束,输入该命令可以看到 hit good trap 的提示,说明程序正常执行结束;命令 q 表示退出 NEMU。

基础设施:简易调试器

由于在刚结束的这一学期,我在所学专业必修的课程《计算机系统设计》已经完成地做完一个 x86 版本的 PA 实验。因此,二周目的时候偷了一些懒,参考了自己之前的一些思路。发现新版本的 PA 实验还是有些区别的。

实现单步执行

在 sdb.c 代码文件,先在 cmd_table 中加入该指令的描述。

然后在外部实现 cmd_si 指令。

具体思路是,利用 strtok 函数对输入按空格进行分词,将判断是否有参数传入。如果没有参数,就只执行一次 cpu_exec(1),如果有参数,将参数转化成十进制表示,并传入 cpu_exec 中,执行对应的次数。

实现结果如下:

实现打印寄存器

在 sdb.c 代码文件,先在 cmd_table 中加入该指令的描述。

然后在外部实现 cmd_info 指令。

参数有 r 和 w,此处暂时实现 r 参数对应的操作,不管 w 参数。直接调用 isa_reg_display() 函数。~~果然,“升级版本”的 PA 实验函数封装得更完善。~~该函数需要自己实现,在 reg.c 中。此时和之前我完成的 x86 版本就不一样了。RISC-V 体系结构的寄存器完全不一样。通过 RTFSC 查看可调用的 api 接口 gpr(),该接口为 reg.h 中的宏定义,根据索引取出对应编号寄存器的值。为了方便展示,同时打印出了寄存器值的十六进制和十进制。

info r 指令的实现结果如下:

实现扫描内存

在 sdb.c 代码文件,先在 cmd_table 中加入该指令的描述。

然后在外部实现 cmd_x 指令。

首先需要通过分词检查参数个数是否合法。

然后得到第二个值。第二个值表示扫描的长度。

第三个值本来应该是一个表达式,此处为了简化,暂时处理为一个十六进制的数字。

调用 vaddr_read 函数对内存进行读取,再输出即可。

该指令的实现结果如下:经查阅相关文档发现,RISC-V 和 x86 一样,也是小端模式编址。

预学习阶段到此结束🎆🎆🎆