前沿
這個是華科研究生課程計算機(jī)系統(tǒng)設(shè)計
一個實驗,做完整個實驗大約花了兩天多的時間吧,其中也是遇到了很多問題,想在博客中記錄下來,日后若是能夠幫助師弟師妹們或者其他的博友,便不負(fù)這篇博客的作用.
這是我整個結(jié)構(gòu)的目錄樹,如果你在接下來閱讀過程中,存在任何路徑上的問題,請仔細(xì)閱讀這四個目錄的位置,這四個目錄是同一級目錄.
+-- nvmain
|
+-- gem5
|
+-- fs-image
|
+-- benchmark
NVMain環(huán)境搭建
下載NVMain
- 下載的網(wǎng)址, https://bitbucket.org/mrp5060/nvmain/src/default/
下載nvmain
,并解壓,將解壓后目錄的名字更改為nvmain
wget https://bitbucket.org/mrp5060/nvmain/get/9c0e87b164bc.zip -O nvmain.zip
unzip nvmain.zip
mv mrp5060-nvmain-9c0e87b164bc nvmain
NVMain
編譯
- 安裝編譯工具
scons
,scons
是一個開放源碼挠阁、以Python語言編碼的自動化構(gòu)建工具靴寂,可用來替代make編寫復(fù)雜的makefile。并且scons是跨平臺的,只要scons腳本寫的好,可以在Linux和Windows下隨意編譯。
sudo apt-get install scons
- 編譯
NVMain
,其中編譯的類型可以選擇fast|debug|prof
三種的任意一種,這里我們選用fast
scons --build-type=fast
如果在編譯的過程中,報了如下的錯誤,無須緊張,這只是個小問題,是因為我們暫時還沒有用到gem5
,所以只需將這個報錯的那一行代碼注釋即可.
scons: Reading SConscript files ...
ImportError: No module named gem5_scons:
File "/home/greek/master/experiment/nvmain/SConstruct", line 253:
SConscript(joinpath(root, 'SConscript'), variant_dir=build_dir)
File "/usr/lib/scons/SCons/Script/SConscript.py", line 614:
return method(*args, **kw)
File "/usr/lib/scons/SCons/Script/SConscript.py", line 551:
return _SConscript(self.fs, *files, **subst_kw)
File "/usr/lib/scons/SCons/Script/SConscript.py", line 256:
call_stack[-1].globals)
File "/home/greek/master/experiment/nvmain/build/SConscript", line 36:
from gem5_scons import Transform
具體的解決辦法是將nvmain/build/SConscript
中的第36
行注釋
# from gem5_scons import Transform # 這行注釋
- 解決錯誤之后,再重新編譯
NVMain
測試
- 指定配置文件,指定測試文件,周期, 命令的格式為:
./nvmain CONFIG_FILE TRACE_FILE [Cycles [PARAM=value]]
這里我們使用的測試命令如下
./nvmain.fast Config/PCM_ISSCC_2012_4GB.config Tests/Traces/hello_world.nvt 1000000
Gem5
環(huán)境搭建
下載Gem5
- 使用常用工具
git
, 但是倉庫在google
中,如果不能翻墻,可能無法下載
git clone https://gem5.googlesource.com/public/gem5
- [推薦使用] 使用工具
hg
.Mercurial
是一款非常優(yōu)秀的分布式版本控制系統(tǒng)(DCVS)褂删,具有高效率、跨平臺冲茸、可擴(kuò)展屯阀、使用簡便且開源等優(yōu)點(diǎn),是目前最為流行的版本控制工具之一.
sudo apt-get install mercurial
hg clone http://repo.gem5.org/gem5
編譯Gem5
- 安裝依賴庫
protobuf-compiler
sudo apt-get install protobuf-compiler
- 編譯
Gem5
,這里可以選擇不同的架構(gòu),比如X86|ARM|ALPHA
, 這里我們選擇使用X86
(我的電腦是X86
結(jié)構(gòu)的,一開始是怕出錯,就編譯了這個,后來感覺應(yīng)該沒啥影響))
scons build/X86/gem5.opt
Gem5
的SE測試
SE測試
- 用來運(yùn)行單個應(yīng)用,一系列指令在MP/SMT上
- 建模用戶可見的ISA和常見的系統(tǒng)調(diào)用
- 模擬系統(tǒng)調(diào)用,通過調(diào)用主機(jī)操作系統(tǒng)
- 簡單的地址翻譯,沒有調(diào)度
./build/X86/gem5.opt configs/example/se.py -c tests/test-progs/hello/bin/x86/linux/hello
Gem5
的FS測試
FS測試
- 能夠啟動完整的操作系統(tǒng)
- 建模硬件設(shè)備
- 中斷,異常,故障處理函數(shù)
參考下載的網(wǎng)址: www.m5sim.org/Download
- 下載
X86
全系統(tǒng)對應(yīng)的文件,一定要下載自己編譯架構(gòu)的對應(yīng)文件
wget http://www.m5sim.org/dist/current/x86/x86-system.tar.bz2
- 解壓文件,同時將解壓后的文件夾放入同一目錄
fs-image
tar vxfj x86-system.tar.bz2
mkdir fs-image
mv binaries fs-image
mv disks fs-image
- 下載
alpha
對應(yīng)的全系統(tǒng)文件,里面包含linux-bigswap2.img
,這個是運(yùn)行的一個必須文件.下載之后,解壓文件,同時將文件拷貝到fs-image/disks
目錄下,可以理解為專門存放鏡像文件的目錄.
wget http://www.m5sim.org/dist/current/m5_system_2.0b3.tar.bz2
tar vxfj m5_system_2.0b3.tar.bz2
cp m5_system_2.0b3/disks/linux-bigswap2.img fs-image/disks
- 運(yùn)行FS模擬
./build/X86/gem5.opt configs/example/fs.py
報了如下的錯誤,這里因為腳本代碼中默認(rèn)了鏡像為x86root.img
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "build/X86/python/m5/main.py", line 453, in main
exec(filecode, scope)
File "configs/example/fs.py", line 340, in <module>
test_sys = build_test_system(np)
File "configs/example/fs.py", line 93, in build_test_system
cmdline=cmdline)
File "/home/greek/master/experiment/gem5/configs/common/FSConfig.py", line 614, in makeLinuxX86System
makeX86System(mem_mode, numCPUs, mdesc, self, Ruby)
File "/home/greek/master/experiment/gem5/configs/common/FSConfig.py", line 539, in makeX86System
disk0.childImage(mdesc.disk())
File "/home/greek/master/experiment/gem5/configs/common/Benchmarks.py", line 63, in disk
return env.get('LINUX_IMAGE', disk('x86root.img'))
File "/home/greek/master/experiment/gem5/configs/common/SysPaths.py", line 71, in __call__
raise IOError("Can't find file '%s' on path." % filename)
IOError: Can't find file 'x86root.img' on path.
解決辦法
這里有兩種解決辦法,推薦使用第二種:
- 修改代碼
gem5/configs/common/Benchmarks.py
中,第63行,修改為如下,其中linux-86.img
為fs-image/disks
目錄下的鏡像文件
return env.get('LINUX_IMAGE', disk('linux-x86.img'))
- [推薦使用] 直接使用參數(shù)指定鏡像文件,使用參數(shù)
--disk-image
以及--kernel
命令使用如下格式:
./build/X86/gem5.opt configs/example/fs.py --disk-image linux-x86.img --kernel x86_64-vmlinux-2.6.22.9
指定M5_PATH
路徑,系統(tǒng)會自動從M5_PATH
路徑中disks
和binaries
兩個目錄尋找指定的鏡像文件和對應(yīng)的內(nèi)核文件.
其中指定M5_PATH
我提供了兩種方式,推薦使用第二種(雖然我用的第一種)):
- 修改
.basrhc
文件,在文件的末尾加入M5_PATH的路徑
export M5_PATH=$M5_PATH:/home/hsc/fs-image
- 直接在運(yùn)行命令中動態(tài)添加環(huán)境變量
M5_PATH=/home/hsc/fs-image ./build/X86/gem5.opt configs/example/fs.py --disk-image linux-x86.img --kernel x86_64-vmlinux-2.6.22.9
linux-x86.img
為fs-image/disks/
目錄下文件, x86_64-vmlinux-2.6.22.9
為fs-image/binaries/
目錄下文件,我看網(wǎng)上很多教程將x86_64-vmlinux-2.6.22.9
改名為vmlinux
,這步的話其實意義不是特別大,不過改了也方便,我這里暫不做修改.
- 連接模擬系統(tǒng)
當(dāng)運(yùn)行了FS測試之后,系統(tǒng)就啟動了,可以通過遠(yuǎn)程連接的方式,連接上我們的系統(tǒng).這里我們介紹兩種方式進(jìn)行連接:
- 利用
gem5
系統(tǒng)自帶的一個連接程序,在gem5/util/term/
目錄中,先進(jìn)行make
,生成可執(zhí)行文件
cd util/term/
make
./m5term localhost 3456
- [推薦使用] 直接利用
telnet
連接系統(tǒng)
telnet localhost 3456
- 退出
這里我依舊使用兩種方法,推薦使用第一種方法
- 在連接的命令行中輸入
m5 exit
- 在連接的命令行中輸入
~.
NVMain和Gem5的集成環(huán)境
Gem5補(bǔ)丁
首先為了消除NVMain
和Gem5
版本之間的差異,所以首先需要給Gem5
打上補(bǔ)丁.(別聽到補(bǔ)丁就覺得畏懼,一開始我有這種感覺,其實后面發(fā)現(xiàn)真的沒啥).
這里我提供兩種方式,兩種方式都很OK
,如果第一種失敗建議換成第二種.如果你的gem5
不是用hg
下載,請換第二種方式.
- 補(bǔ)丁文件已經(jīng)在
nvmain/patches/gem5
目錄中給出,在gem5
目錄中執(zhí)行下列命令
hg qinit
hg qimport -f ../nvmain/patches/gem5/nvmain2-gem5-11688+
hg qpush
nvmain2-gem5-11688+
這個是補(bǔ)丁文件的名字,在nvmain/patches/gem5
中,以后可能是其他的版本,換成自己的版本補(bǔ)丁名字即可.
- 手動補(bǔ)丁,直接查看補(bǔ)丁的內(nèi)容,手動修改文件,如下所示為補(bǔ)丁的內(nèi)容
diff --git a/configs/common/Options.py b/configs/common/Options.py
--- a/configs/common/Options.py
+++ b/configs/common/Options.py
@@ -63,6 +63,12 @@
# being used, and consequently no CPUs, but rather various types of
# testers and traffic generators.
def addNoISAOptions(parser):
+ # Check for extra nvmain configuration override options
+ for arg in sys.argv:
+ if arg[:9] == "--nvmain-":
+ parser.add_option(arg, type="string", default="NULL",
+ help="Set NVMain configuration value for a parameter")
+
parser.add_option("-n", "--num-cpus", type="int", default=1)
parser.add_option("--sys-voltage", action="store", type="string",
default='1.0V',
只需在gem5/configs/common/Options.py
的第82行之后,加入如下三行
for arg in sys.argv:
if arg[:9] == "--nvmain-":
parser.add_option(arg, type="string", default="NULL", help="Set NVMain configuration value for a parameter")
NVMain和Gem5混合編譯
- 編譯命令
scons EXTRAS=../nvmain build/X86/gem5.opt
- 測試,其中混合編譯成功的一個重要標(biāo)志是可以使用
--mem-type=NVMainMemory
參數(shù)來指定主存為NVM
M5_PATH=/home/hsc/fs-image ./build/X86/gem5.opt configs/example/se.py -c tests/test-progs/hello/bin/x86/linux/hello --caches --l2cache --mem-type=NVMainMemory --nvmain-config=../nvmain/Config/PCM_ISSCC_2012_4GB.config
PARSEC 測試
我參考的資料是: pfzuo.github.io/2016/06/06/Configure-and-run-parsec-2.1-benchmark-in-GEM5/
這一層我的目錄結(jié)構(gòu)為
+-- fs-image/
| +-- disks/
| | +-- linux-bigswap2.img
| | +-- x86root-parsec.img
| +-- binaries/
| | +-- x86_64-vmlinux-2.6.28.4-smp
| | +-- tsb_osfpal
+-- benchmark
| +-- TR-09-32-parsec-2.1-alpha-files
- 下載
kernel
文件,復(fù)制到fs-image/binaries
目錄下
wget http://www.cs.utexas.edu/~parsec_m5/x86_64-vmlinux-2.6.28.4-smp
- 下載PARSEC對應(yīng)的PAL code文件, 復(fù)制到
fs-image/binaries
目錄下
wget http://www.cs.utexas.edu/~parsec_m5/tsb_osfpal
- 下載PARSEC-2.1 Disk Image并解壓到
fs-image/disks
目錄中
wget http://www.cs.utexas.edu/~parsec_m5/x86root-parsec.img.bz2
bzip2 -d x86root-parsec.img.bz2
- 下載PARSEC script生成包轴术,并解壓到
benchmark
目錄下
wget http://www.cs.utexas.edu/~parsec_m5/TR-09-32-parsec-2.1-alpha-files.tar.gz
tar zxvf TR-09-32-parsec-2.1-alpha-files.tar.gz
- 生成
script
./writescripts.pl <benchmark> <nthreads>
其中benchmark包含如下
blackscholes
bodytrack
canneal
dedup
facesim
ferret
fluidanimate
freqmine
streamcluster
swaptions
vips
x264
rtview
我生成的腳本是blackscholes
,利用gem5
進(jìn)行全系統(tǒng)測試
./writescripts.pl blackscholes 4
- 運(yùn)行生成的腳本
M5_PATH=/home/hsc/fs-image ./build/X86/gem5.opt ./configs/example/fs.py -n 2 --script=../benchmark/TR-09-32-parsec-2.1-alpha-files/blackscholes_4c_test.rcS --disk-image x86root-parsec.img --kernel x86_64-vmlinux-2.6.28.4-smp
- 連接測試
telnet localhost 3456
FPC壓縮方案
在修改NVMain
源代碼實現(xiàn)FPC壓縮方案時,我走了不少的彎路.一開始看了實驗推薦的一篇關(guān)于FPC
的論文,但是確實看的特別懵,感覺不是很清楚作者壓縮的思路.究其原因,作者沒有在論文中給出一個具體的實例,導(dǎo)致理解起來有些困難.在此,我還得特別感謝坐我旁邊的魏博士,是他推薦了一篇更新的關(guān)于FPC
的論文,并且論文中給出了詳細(xì)的實例,便于理解.并給我梳理NVMain
的一個整體框架,使我能夠快速找到修改的位置.
參考論文,推薦重點(diǎn)看第二篇
- Frequent Pattern Compression: A Significance-Based Compression Scheme for L2 Caches
- CompEx: Compression-expansion coding for energy, latency, and lifetime improvements in MLC/TLC NVM
修改位置
打了箭頭的位置,即為修改的位置,其中FPCompress
為自己創(chuàng)建的文件夾,并新建的類,及其實現(xiàn).
+-- nvmain/
| +-- DataEncoders/
| | +-- FlipNWrite/
| | | +-- FlipNWrite.h
| | | +-- FlipNWrite.cpp
| | | +-- SConscript
| | +-- FPCompress/ <-
| | | +-- FPCompress.h <-
| | | +-- FPCompress.cpp <-
| | | +-- SConsript <-
| | +-- DataEncoderFactory.cpp <-
其實整體的思路可以理解為,要寫入的數(shù)據(jù),其中默認(rèn)有一種編碼方式,即不進(jìn)行任何操作,而FlipNWrite
為另一種編碼方式,我們只需要模仿FlipNWrite
實現(xiàn)自己的編碼方案,在編碼方式中實現(xiàn)自己的FPC
壓縮,就可以相當(dāng)于攔截寫入的數(shù)據(jù),并替換成自己的壓縮后的數(shù)據(jù),成功寫入.
/* FPCompress.h
* Author: hsc
* date: 2019-10-9
*/
#ifndef __NVMAIN_FPCOMPRESS_H__
#define __NVMAIN_FPCOMPRESS_H__
#include "src/DataEncoder.h"
namespace NVM {
class FPCompress : public DataEncoder {
public:
FPCompress();
~FPCompress();
void SetConfig( Config *config, bool createChildren = true );
ncycle_t Read( NVMainRequest *request );
ncycle_t Write( NVMainRequest *request );
void RegisterStats( );
private:
uint64_t p0; /* pattern zero */
uint64_t p1;
uint64_t p2;
uint64_t p3;
uint64_t p4;
uint64_t p5;
uint64_t p6;
uint64_t p7; /* pattern seven */
void set_data( uint8_t *address, uint64_t index, uint64_t data);
};
/* zero run */
static inline bool pattern_zero(int64_t x){
return x == 0;
}
/* 8bit sign extended */
static inline bool pattern_one(int64_t x){
return (x >> 7) == -1 || (x >> 7) == 0;
}
/* 16 bit sign extended */
static inline bool pattern_two(int64_t x){
return (x >> 15) == -1 || (x >> 15) == 0;
}
/* halfword sign extended */
static inline bool pattern_three(int64_t x){
return (x >> 31) == -1 || (x >> 31) == 0;
}
/* halfword padded with a zero halfword */
static inline bool pattern_four(int64_t x){
return (x << 32) == 0;
}
/* two half words, each a byte sign-extended */
static inline bool pattern_five(int64_t x){
int32_t high = static_cast<int32_t>(x >> 32);
int32_t low = static_cast<int32_t>(x & 0xffffffff);
bool high_valid = (high >> 15) == -1 || (high >> 15) == 0;
bool low_valid = (low >> 15) == -1 || (low >> 15) == 0;
return high_valid && low_valid;
}
/* word consisting of repeated bytes */
static inline bool pattern_six(int64_t x){
int64_t total = 0;
int64_t pattern_byte = x & 0xffff;
total |= pattern_byte;
total <<= 16;
total |= pattern_byte;
total <<= 16;
total |= pattern_byte;
total <<= 16;
total |= pattern_byte;
total <<= 16;
return total == x;
}
};
#endif
其中,我覺得比較重要的是實現(xiàn)Write
, RegisterStats
這兩個函數(shù),一個用來實現(xiàn)壓縮方案,一個用來打印各種數(shù)據(jù)模式的統(tǒng)計量.在各種模式的判別中,一開始我是讀的uint64_t
無符號類型的,發(fā)現(xiàn)這樣判斷實現(xiàn)的不是很優(yōu)雅,后來換成了帶符號數(shù)據(jù)int64_t
.
/* FPCompress.cpp
* Author : hsc
* date: 2019-10-9
*/
#include "DataEncoders/FPCompress/FPCompress.h"
using namespace NVM;
FPCompress::FPCompress()
{
/* clear statistics */
p0 = 0;
p1 = 0;
p2 = 0;
p3 = 0;
p4 = 0;
p5 = 0;
p6 = 0;
p7 = 0;
}
FPCompress::~FPCompress()
{
/*
* nothing to do here
*/
}
void FPCompress::SetConfig( Config *config, bool /*createChildren*/ )
{
Params *params = new Params( );
params->SetParams( config );
SetParams( params );
}
void FPCompress::RegisterStats()
{
AddStat(p0);
AddStat(p1);
AddStat(p2);
AddStat(p3);
AddStat(p4);
AddStat(p5);
AddStat(p6);
AddStat(p7);
}
ncycle_t FPCompress::Read( NVMainRequest *request )
{
ncycle_t rv = 0;
// TODO: Add some energy here
return rv;
}
ncycle_t FPCompress::Write( NVMainRequest *request ){
NVMDataBlock& newData = request->data;
NVMDataBlock& oldData = request->oldData;
int64_t *ptr = (int64_t*)(newData.rawData);
uint64_t offset = 0, size = 0, encode_data = 0;
while(size < newData.GetSize()){
int64_t dword = *ptr;
if(pattern_zero(dword)){
encode_data = 0;
set_data(oldData.rawData, offset, encode_data);
p0++; /* zero statistics */
offset += 3;
}else if(pattern_one(dword)){
encode_data = dword & 0xff;
encode_data |= (0x1 << 8);
set_data(oldData.rawData, offset, encode_data);
p1++;
offset += 11;
}else if(pattern_two(dword)){
encode_data = dword & 0xffff;
encode_data |= (0x2 << 16);
set_data(oldData.rawData, offset, encode_data);
p2++;
offset += 19;
}else if(pattern_three(dword)){
encode_data = dword & 0xffffffff;
encode_data |= ((uint64_t)0x3 << 32);
set_data(oldData.rawData, offset, encode_data);
p3++;
offset += 35;
}else if(pattern_four(dword)){
encode_data = dword >> 32;
encode_data |= ((uint64_t)0x4 << 32);
set_data(oldData.rawData, offset, encode_data);
p4++;
offset += 35;
}else if(pattern_five(dword)){
encode_data = (dword & 0xffff);
encode_data |= ((dword >> 32) & 0xffff) << 16;
encode_data |= ((uint64_t)0x5 << 32);
set_data(oldData.rawData, offset, encode_data);
p5++;
offset += 35;
}else if(pattern_six(dword)){
encode_data = dword && 0xffff;
encode_data |= (0x6 << 16);
set_data(oldData.rawData, offset, encode_data);
p6++;
offset += 19;
}else {
encode_data = dword;
set_data(oldData.rawData, offset, (encode_data >> 32) & 0xffffffff);
set_data(oldData.rawData, offset + 32, (encode_data & 0xffffffff));
p7++;
offset += 64;
}
/* pointer to next dword */
ptr++;
size += 8;
}
}
void FPCompress::set_data( uint8_t *address, uint64_t offset, uint64_t data)
{
uint64_t *ptr = (uint64_t*)(address + (offset / 8));
uint64_t new_data = *ptr;
uint64_t len = offset - (offset / 8) * 8;
new_data = (new_data << (64 - len)) >> (64 - len);
new_data |= (data << len);
*ptr = new_data;
}
思路
-
request
中包含的是uint8_t
類型的指針地址,將其強(qiáng)制轉(zhuǎn)換為int64_t
類型的指針,即可讀取64bit
數(shù)據(jù) - 讀出數(shù)據(jù),判斷是8種數(shù)據(jù)模式中的哪一種數(shù)據(jù)模式,然后構(gòu)造壓縮后的數(shù)據(jù)
- 因為壓縮的數(shù)據(jù),特別不規(guī)整,有
3bit
,11bit
,19bit
,35bit
不等.比如我當(dāng)前要寫3bit
,這3bit
可能還跨了不同的字節(jié),因此導(dǎo)致情況特別復(fù)雜. - 我的方法是計算寫入位置最近的以字節(jié)為單位對齊的偏移地址, 從該偏移地址中讀取64
bit
數(shù)據(jù),這樣的話,這64bit
中最多包含7位之前寫入的數(shù)據(jù).我們通過將之前寫入的數(shù)據(jù),和壓縮后的數(shù)據(jù)進(jìn)行合并成一個新的64bit
數(shù)據(jù),然后重新寫入.
在DataEncoderFactory.cpp
中加入自己的編碼方式
// DataEncoderFactory.cpp
DataEncoder *DataEncoderFactory::CreateDataEncoder( std::string encoderName )
{
DataEncoder *encoder = NULL;
if( encoderName == "default" ) encoder = new DataEncoder( );
else if( encoderName == "FlipNWrite" ) encoder = new FlipNWrite( );
else if( encoderName == "FPCompress") encoder = new FPCompress( );
return encoder;
}
為了使用我們的編碼方式,我們需要在配置文件中指定編碼方式,這里我選用的配置文件為nvmain/Config/PCM_MLC_example.config
,在文件的末尾,加入如下內(nèi)容:
DataEncoder FPCompress
編譯
scons --build-type=fast
測試
利用我們剛剛修改的配置文件nvmain/Config/PCM_MLC_example.config
進(jìn)行測試
./nvmain.fast Config/PCM_MLC_example.config Tests/Traces/hello_world.nvt 1000000
數(shù)據(jù)寫入統(tǒng)計
我是若木,倘若大家有更好的實現(xiàn)方式,記得與我聯(lián)系,也可參觀一下我的博客.