目录
参考资料
一、简介
【注:一些可能涉及到的主要思想】
1、安装软件依赖
2、下载源码
3、编译gem5
4、在gem5/include写sourceCode.cpp
5、将待执行文件编译成可执行文件
6、将可执行文件test.out放入镜像中
① 挂载镜像文件
② 将test.out和a.out都复制到镜像中
③ 删除挂载点
7、执行img镜像中的可执行文件
① 在gem5的根目录中执行:
② 启动M5端,使用m5term远程监听(稳定性更好)【首次使用到③,非首次到④】
③ 如果为首次使用m5term,执行以下操作
④ 如果非首次使用m5term,执行以下操作
⑤进入镜像文件
⑥ 返回到img文件的根目录中,执行test.out文件
⑦ 执行结果(每个chiplet的模拟都是两个窗口,后续可以考虑直接在原窗口进入到程序中,不知道是否可行)
8、得到两个chiplet的communication文件
9、将communication文件转为trace文件
三、popnet操作手册
1、下载源码
2、编译popnet
3、将bench文件移动到popnet/two_trace目录下
4、执行popnet仿真
5、实验结果
本文主要基于上述材料进行复现,记录下来主要是为了便于复盘。帖子中仍存在了解不详细的部分,也希望大家可以为我指出错误,让我能学习进步。
本实验流程基础:《基于多芯粒集成的 X86 指令集共享式储存仿真器说明书》
基于gem5和popnet构建的基于多芯粒集成的CPU共显示储存仿真器,能够支持精确互连模型的大规模并行模拟。
具体细节可以了解文档。
需要提前准备的文件:
制作好的ubuntu镜像文件。
可直接使用的img文件地址:我用夸克网盘分享了「gapbs.img」,点击链接即可保存。链接:夸克网盘分享
linux内核文件
可直接使用的vmlinux内核文件地址:我用夸克网盘分享了「vmlinux-4.19.83」,点击链接即可保存。链接:夸克网盘分享
以下所有操作都是在该项目的gem5目录中完成。
Chiplet的主要思想是:
如果你想在镜像中执行某个程序,需要将程序的可执行文件首先放到img镜像里。
然后找个文件夹挂载镜像文件。
然后再删除镜像文件的挂载点。
此时可以使用前面编译好的gem5.opt和vmlinux内核文件进入到远程连接的地方。在gem5根目录下执行命令行:build/X86/gem5.opt configs/example/fs.py --kernel=include/vmlinux-4.19.83 --disk-image=include/gapbs.img -n 3,会出现系统正在监听3456端口。不对当前页面进行任何操作。
在m5端进行连接。打开一个新的窗口(可以使用相同的用户名连接到相同的ip上),然后进入到gem5根目录下的util/term目录,命令行make gcc -o mterm term.c编译,再安装:
make install
sudo install -o root -m 555 m5term /usr/local/bin
安装完成后直接使用m5term localhost 3456远程端口监听连接模拟系统。
这之后就需要等待大概15分钟,只到默认的根目录命令行出现。
如果不再需要使用的话,可以直接使用m5 exit退出(也可以直接在被监听的窗口退出该命令即可)
将需要执行的程序编译并挂载的步骤:
我们可以在include目录下直接执行sudo mount -o loop,offset=1048576 ./gapbs.img /mnt,把挂载的目录放到了根目录下。【可以新建挂载目录】【但是感觉在当前目录中mkdir mnt新建一个mnt目录,然后再sudo mount -o loop,offset=1048576 ./gapbs.img /mnt就可以直接挂载到当前目录了。省略了来回跳转的麻烦】
编译文件:g++ -L./ test.cpp -o test.out -lm5
移动可执行文件:sudo cp ./test.out /mnt,【这个时候一定要注意移动的路径】
完成所有修改以后,就要使用umount命令卸载img文件:sudo umount /mnt
然后安装上面的教程启动gem5,再使用m5连接后进入远程host
直接进入到可执行文件所在的目录中,直接执行:/a.out
如果需要修改镜像文件的话,需要进行如下步骤:
在gem5根目录下执行build/X86/gem5.opt configs/example/fs.py --kernel=include/vmlinux-4.19.83 --disk-image=include/gapbs.img -n 3,会提示没有six模块。
安装six模块:使用echo $PYTHONPATH检查 PYTHONPATH 环境变量,输出为空,添加six包的路径到PATH中,export PYTHONPATH=$PYTHONPATH:/home/zyl/.local/lib/python3.8/site-packages,这时候再重新执行①中的命令就可以了。【不知道为什么再次登录打开的时候就需要重新执行这个步骤。有没有一种方法可以直接将这个路径添加到系统文件中?可以写到.bashrc文件中,将这行export写到文件的最下方,然后命令行中执行. .bashrc将命令生效】
重新执行后会提示****REAL SIMULATION****,此时不要进行任何操作,否则可能会退出。
打开新的终端(在我的SSH中就是打开新的SSH连接),登录上后cd home/zyl/chiplet_popnet/Chiplet_Gem5_SharedMemory/util/term跳转到m5端登录。
如果是首次执行,需要按照(1)make gcc -o mterm term.c;(2)sudo make install;(3)sudo install -o root -m 555 m5term /usr/local/bin;(4)m5term localhost 3456流程进行完整的四步操作。但如果不是首次,可以直接在当前目录中输入(4)m5term localhost 3456即可进入到远程加载初始化中。
加载大概需要15分钟左右【再次执行后的启动时间变成了30分钟】,等待,不要进行任何操作。
加载完成后会提示使用默认的用户名root根目录的命令行root@gem5-host:/home/gem5/gapbs#后就可以进行下一步操作了。
导航到可执行文件的目录,直接放在根目录得了,然后直接/test.out执行这个可执行文件。
sudo apt install build-essential git m4 scons zlib1g zlib1g-dev libprotobuf-dev protobuf-compiler libprotoc-dev libgoogle-perftools-dev python3-dev python3-six
python3 libboost-all-dev pkg-config
如果安装过程中出现问题,就按照提示修改后单个安装。【单个安装不会出现很多问题】
git clone https://github.com/FCAS-SCUT/Chiplet-Gem5-SharedMemory.git
目录格式(其中有一部分是我完成流程后截图的)
在gem5的根目录中执行编译。
sudo scons build/X86/gem5.opt -j 4
-j表示开启多线程,4表示线程数
编译完成【大概需要1h左右】
测试:
./build/X86/gem5.opt ./configs/example/se.py -c ./tests/test-progs/hello/bin/x86/linux/hello
#include "m5ops.h"
int main(){
m5_gadia_call(0, 1, 2, 0); //chiplet0 send data2 to target chiplet1
uint64_t result = m5_gadia_receive(0);
return 0;
}
其中m5ops.h需要写入正确的位置。【我是直接把文件复制过来的】
对sourceCode.cpp文件进行编译,输出为a.out
g++ -L/Chiplet-Gem5-SharedMemory/gem5/include ./sourceCode.cpp -o a.out -lm5
以矩阵乘法为例,执行代码test.cpp如下:
#include
#include
#include
#include
using namespace std;
#include "m5ops.h"
/*
本示例示范 2 个 chiplet 做 10*10 的矩阵乘工作,假设结果为 C,则 C 的大小为
10*10,用一维矩阵储存,则 chiplet0 计算矩阵乘 C 索引从 0 到 500*249 的结果,而
chiplet1 计算剩下的结果。
*/
extern "C"
{
const int N = 10;
int main() {
// 程序初始化开始
long long *martrix = (long long *)malloc(N * N * sizeof(long long));
for (int i = 0; i < N * N; i++) {
srand(i);
martrix[i] = rand() % 10;
}
cout << "init martrix" << endl;
long long *martrix2 = (long long *)malloc(N * N * sizeof(long long));
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
srand(i + j);
martrix2[i * N + j] = rand() % 10;
}
}
cout << "init martrix2" << endl;
long long *martrix3 = (long long *)malloc(N * N * sizeof(long long));
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
for (int k = 0; k < N; k++)
martrix3[i * N + j] = 0;
}
}
cout << "init martrix3" << endl;
// 初始化结束
// 初始化 gem5 的相关信息
int chipletNumber = -1;
cout << "enter the ChipletNumber" << endl;
std::cin >> chipletNumber;
// 完成 gem5 的相关信息初始化
m5_gadia_call(chipletNumber, chipletNumber, 0, 0); // 记录启始 cycle
// 示例的 totalChipletNumber 为 2,故不显式的写出来。
if (chipletNumber == 0) {
for (int i = N / 2; i < N; i++) {
for (int j = 0; j < N; j++) {
for (int k = 0; k < N; k++)
martrix3[i * N + j] = martrix3[i * N + j] + martrix[i * N + k] * martrix2[k * N + j];
}
}
m5_gadia_call(chipletNumber, 1, -2, 0);
int position = 0;
cout << " coming 0" << "coming while" << endl;
while (true) {
int result = (int)m5_gadia_receive(chipletNumber);
// 检测 chiplet1 是否完成了矩阵乘的工作
if(result == -1)
continue;
else if (result == -2) //代表等待的Chiplet已经完成读写
break;
else {
martrix3[position] = result;
position++;
}
}
m5_gadia_call(chipletNumber, chipletNumber, 0, 0); // 记录结束 cycle
return 0;
}// the following is responsible for collect
else if (chipletNumber == 1) {
for (int i = 0; i < N / 2; i++) {
for (int j = 0; j < N; j++) {
for (int k = 0; k < N; k++)
martrix3[i * N + j] = martrix3[i * N + j] + martrix[i * N + k] * martrix2[k * N + j];
// chiplet 1 把结果写入到共享储存中。
m5_gadia_call(chipletNumber, 0, (int)martrix3[i * N + j], 0);
}
}
cout << "coming 1" << endl;
// 告诉 chiplet0, chiplet1 已经完成矩阵乘法
m5_gadia_call(chipletNumber, 0, -2, 0);
return 0;
}
}
}
执行编译:
g++ -L./ test.cpp -o test.out -lm5
//正常来说-lm5应该是放在前面的,但是在这里编译的时候放到前面会提示undefined reference to 'm5_gadia_call'错误,将-lm5放在后面就可以正常编译
此时在include目录中得到test.out文件。
【如果在当前的目录中执行可能会遇到Illegal instruction (core dumped)报错,这是正常的,文件可以在img镜像中正确执行】
【因为实验中使用了两个chiplet进行仿真,所以需要按照以下步骤制作两个镜像文件,img1用于仿真chiplet0,img2用来仿真chiplet1,为了简便,本说明文档中只示范了一个镜像文件的执行步骤,但结果中仍使用两个chiplet进行呈现】
将前文中提到的img镜像文件和vmlinux文件下载到gem5/include目录中。
sudo mount -o loop,offset=1048576 ./gapbs.img /mnt
挂载在根目录的mnt/目录中。
sudo cp ./a.out /mnt/
sudo cp ./test.out /mnt/
查看mnt目录
sudo umount /mnt
此时就不能再查看目录了。
build/X86/gem5.opt configs/example/fs.py --kernel=vmlinux-4.19.83 --diskimage=gapbs.img -n 1
//使用一个核
此时当前窗口的显示页面如下:
表明可以使用4567端口进行远程监听。
新打开一个窗口,进入到cd home/zyl/chiplet_popnet/Chiplet_Gem5_SharedMemory/util/term。
就是当前的gem5/util/term目录中。
编译:
make gcc -o mterm term.c
安装:
sudo make install
sudo install -o root -m 555 m5term /usr/local/bin
远程监听:
m5term localhost 3456
大概需要等待30分钟左右。
远程监听:
m5term localhost 3456
大概需要等待30分钟左右。
此时已经进入到img镜像中。
img1-chiplet1:
img2-chiplet0:
两个chiplet执行结束后,在为img1执行build/X86/gem5.opt configs/example/fs.py --kernel=vmlinux-4.19.83 --diskimage=gapbs.img -n 1所在目录中出现了两个新的communication文件。
使用count.cpp文件。[说明教程中给的名字是convertpop.cpp,但名字无所谓]
在communication文件所在的目录中新增一个count.cpp文件:
// 此程序用于统计共享储存的通信文件并输出 popnet 的输入文件
// 该程序的工作原理是 1 将所有共享存储的通信记录一次性读入程序中。 2
// 读入完成后根据通信记 录的目标 chiplet 编号对通信记录分类。 3 分类之后按 cycle
// 排序。 4 选择 chiplet0 的 cycle,将其 他 chiplet 的 cycle 向 chiplet0
// 对齐,即同步大家的 cycle。 4 将所有 chiplet 的 cycle 排序,将通信 记录输出成
// trace 文件”bench”。 5 将各自 chiplet 在 3 中分类的而形成的类别,分文件输出。
#include
#include
#include
#include
#include
#include
class CommunicationRecord;
class Chiplet {
public:
void setTime(unsigned long long time) { this->chipletStartCycle = time; }
Chiplet() : chipletStartCycle(0), chipletNumber(-1) {
// do nothing
}
int chipletNumber;
unsigned long long chipletStartCycle;
std::vector
relatedRecord; // if the srcCore number is the ChipletNumber, the record will be put here //
};
class CommunicationRecord {
public:
CommunicationRecord() : cycle(0), srcCore(0), targetCore(0) {}
CommunicationRecord(unsigned long long _cycle, int _srcCore,
int _targetCore) {
this->cycle = _cycle;
this->srcCore = _srcCore;
this->targetCore = _targetCore;
}
unsigned long long cycle;
int srcCore;
int targetCore;
};
/*
* this Program is aimed to generate the popnet trace file from the
* communication record
*/
std::string communicationBaseFileName = "communication";
int TotalChipletNumber = 0;
unsigned long long chipletNumberZeroStartCycle;
void sortTheChipletRecord(unsigned long long *cycle, int *sequency, int length);
// 输出bench0.0 bench0.1 等文件
void outputSenderFile(Chiplet *ChipletSet) {
for (size_t j = 0; j < ::TotalChipletNumber; j++) {
std::ofstream file;
int chipletNumber = j;
std::string baseName = "bench.0.";
baseName += char(chipletNumber + '0');
file.open(baseName.c_str(), std::ios::out);
// look for the record whose srcCore is j
std::vector tmpRecordSet;
for (size_t i = 0; i < ::TotalChipletNumber; i++) {
for (size_t k = 0; k < ChipletSet[i].relatedRecord.size(); k++) {
CommunicationRecord *tmp = ChipletSet[i].relatedRecord[k];
if (tmp->srcCore == chipletNumber) {
tmpRecordSet.push_back(tmp);
}
}
}
// sort the record
CommunicationRecord *record2sort =
new CommunicationRecord[tmpRecordSet.size()]();
int *sequency = new int[tmpRecordSet.size()]();
unsigned long long *cycle = new unsigned long long[tmpRecordSet.size()];
for (size_t j = 0; j < tmpRecordSet.size(); j++) {
record2sort[j] = *tmpRecordSet[j];
sequency[j] = j;
cycle[j] = tmpRecordSet[j]->cycle;
}
sortTheChipletRecord(cycle, sequency, tmpRecordSet.size());
for (int i = 0; i < tmpRecordSet.size(); i++) {
record2sort[i] = *tmpRecordSet[sequency[i]];
}
// write to the sender file
for (int i = 0; i < tmpRecordSet.size(); i++) {
unsigned long long cycle = (record2sort[i].cycle - chipletNumberZeroStartCycle) / 1000;
int targetCore = record2sort[i].targetCore;
file << cycle << " 0 " << j << " 0 " << targetCore << " 5" << std::endl;
}
file.close();
}
}
// 输出bench文件
void outputTheFile(CommunicationRecord *Record, int length) {
std::ofstream file;
file.open("bench", std::ios::out);
for (size_t i = 0; i < length; i++) {
unsigned long long cycle =(Record[i].cycle - chipletNumberZeroStartCycle) / 1000;
int srcCore = Record[i].srcCore;
int targetCore = Record[i].targetCore;
file << cycle << " 0 " << srcCore << " 0 " << targetCore << " 5"
<< std::endl;
}
file.close();
}
// 用于生成全部trace文件
void generatePopnetTraceFile(Chiplet *chipletSet) {
int recordSize = 0;
for (size_t i = 0; i < ::TotalChipletNumber; i++) {
recordSize += chipletSet[i].relatedRecord.size();
}
// bool* recordComplete = new bool[::TotalChipletNumber]();
int *ptr2ChipletRecord = new int[::TotalChipletNumber]();
CommunicationRecord *TotalRecord = new CommunicationRecord[recordSize]();
for (size_t i = 0; i < recordSize; i++) {
bool init = false;
CommunicationRecord tmpRecord = CommunicationRecord(0, 0, 0);
int targetChipletNumber = -1;
for (size_t j = 0; j < ::TotalChipletNumber; j++) {
if (ptr2ChipletRecord[j] >= chipletSet[j].relatedRecord.size()) {
continue;
}
if (!init) {
tmpRecord = *chipletSet[j].relatedRecord[ptr2ChipletRecord[j]];
init = true;
targetChipletNumber = j;
}
// compare the time and decide the min
if ((chipletSet[j].relatedRecord[ptr2ChipletRecord[j]])->cycle <
tmpRecord.cycle) {
tmpRecord = *chipletSet[j].relatedRecord[ptr2ChipletRecord[j]];
targetChipletNumber = j;
}
}
TotalRecord[i] = tmpRecord;
ptr2ChipletRecord[targetChipletNumber]++;
}
outputTheFile(TotalRecord, recordSize);
outputSenderFile(&chipletSet[0]);
}
void swap(int *first, int *second) {
int *tmp = first;
second = tmp;
first = second;
}
void swap(unsigned long long *first, unsigned long long *second) {
unsigned long long *tmp = first;
second = tmp;
first = second;
}
void sortTheChipletRecord(unsigned long long *cycle, int *sequency,
int length) {
// buble sort because not requring high performance
for (int i = 0; i < length - 1; i++) {
for (int j = 0; j < length - 1; j++) {
if (cycle[j] > cycle[j + 1]) {
swap(&cycle[j], &cycle[j + 1]);
swap(&sequency[j], &sequency[j + 1]);
}
}
}
}
// 排序时间,防止出现cycle无法对齐的情况
void sortChipletTime(Chiplet *chipletSet) {
for (size_t i = 0; i < ::TotalChipletNumber; i++) {
// for each chiplet
Chiplet *currentChiplet = &chipletSet[i];
int size = currentChiplet->relatedRecord.size();
CommunicationRecord *tmp = new CommunicationRecord[size];
int *sequency = new int[size]();
unsigned long long *cycle = new unsigned long long[size];
for (size_t j = 0; j < size; j++) {
tmp[j] = *currentChiplet->relatedRecord[j];
sequency[j] = j;
cycle[j] = currentChiplet->relatedRecord[j]->cycle;
}
sortTheChipletRecord(cycle, sequency, (chipletSet[i]).relatedRecord.size());
currentChiplet->relatedRecord.clear();
for (int i = 0; i < size; i++) {
currentChiplet->relatedRecord.push_back(&tmp[sequency[i]]);
}
}
}
// 查找chiplet的开始时间,因为gem5启动操作系统一般需要几十万cycle的开销。
void initChipletStartPoint(Chiplet *chiplet, bool isMainChiplet = false,
unsigned long long mainChipletCycle = 0) {
// here is some stuff that something must be fixed in the future
chiplet->setTime(chiplet->relatedRecord[0]->cycle);
if (!isMainChiplet) {
unsigned long long base = chiplet->relatedRecord[0]->cycle;
chiplet->relatedRecord[0]->cycle = chiplet->relatedRecord[0]->cycle - base +
mainChipletCycle + (rand() % 20) * 1000;
for (size_t i = 1; i < chiplet->relatedRecord.size(); i++) {
chiplet->relatedRecord[i]->cycle =
chiplet->relatedRecord[i]->cycle - base + mainChipletCycle;
}
} else {
}
}
void processOneFile(Chiplet *chipletSet, int currentChipletNumber) {
std::ifstream myFile;
std::string realFile =
communicationBaseFileName + (char)(currentChipletNumber + '0');
myFile.open(realFile.c_str(), std::ios::in);
std::stringstream ss;
std::string line = "";
while (std::getline(myFile, line)) {
ss.clear();
ss.str(line);
unsigned long long cycle;
int srcCoreNumber;
int targetCoreNumber;
int data; // we don't need it but for skipping
ss >> cycle >> srcCoreNumber >> targetCoreNumber >> data;
if (targetCoreNumber == -1) { // the message is for all
CommunicationRecord *tmp = new CommunicationRecord(cycle, srcCoreNumber, currentChipletNumber);
for (size_t i = 0; i < ::TotalChipletNumber; i++) {
chipletSet[i].relatedRecord.push_back(tmp);
}
} else {
chipletSet[targetCoreNumber].relatedRecord.push_back(
new CommunicationRecord(cycle, srcCoreNumber, currentChipletNumber));
}
}
myFile.close();
}
int main() {
std::cout << "Enter the TotalChipletNumber" << std::endl;
std::cin >> ::TotalChipletNumber;
// bool* chipletInit = new bool[TotalChipletNumber]();
Chiplet *myChipletSet = new Chiplet[::TotalChipletNumber]();
for (int i = 0; i < TotalChipletNumber; i++) {
myChipletSet[i].chipletNumber = i;
}
for (int i = 0; i < ::TotalChipletNumber; i++) {
processOneFile(myChipletSet, i);
}
sortChipletTime(myChipletSet);
initChipletStartPoint(&myChipletSet[0], true);
for (size_t i = 1; i < ::TotalChipletNumber; i++) {
initChipletStartPoint(&myChipletSet[i], false,
myChipletSet[0].chipletStartCycle);
}
chipletNumberZeroStartCycle = myChipletSet[0].relatedRecord[0]->cycle;
generatePopnetTraceFile(myChipletSet);
return 0;
}
编译count.cpp:
g++ -L./ ./count.cpp -o count.out
执行count.out文件,得到bench文件【这个bench文件就是pop的输入】
cheiplet仿真结束。
git clone https://gitee.com/hic_0757/popnet_modified.git
mkdir build
cd build
cmake ..
make
cp /home/zyl/chiplet_popnet/Chiplet-Gem5-SharedMemory/gem5/bench /hom e/zyl/chiplet_popnet/popnet_modified/two_trace/
build/popnet -A 16 -c 1 -V 3 -B 8 -O 8 -F 4 -L 1000 -T 2000 -r 1 -I two_trace/bench -R 0
以上为popnet的全仿真流程,在运行的过程中会出现各种各样的问题如缺少six模块,undefined reference,程序执行时间过长等,这些不具备通性,如果有老师同学在执行的过程中存在任何问题,可以随时交流。