1#*************************************************************************************** 2# Copyright (c) 2020-2021 Institute of Computing Technology, Chinese Academy of Sciences 3# 4# XiangShan is licensed under Mulan PSL v2. 5# You can use this software according to the terms and conditions of the Mulan PSL v2. 6# You may obtain a copy of Mulan PSL v2 at: 7# http://license.coscl.org.cn/MulanPSL2 8# 9# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 10# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 11# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 12# 13# See the Mulan PSL v2 for more details. 14#*************************************************************************************** 15 16# Simple version of xiangshan python wrapper 17 18import os 19import argparse 20import sys 21import subprocess 22 23 24class XSArgs(object): 25 script_path = os.path.realpath(__file__) 26 # default path to the repositories 27 noop_home = os.path.join(os.path.dirname(script_path), "..") 28 nemu_home = os.path.join(noop_home, "../NEMU") 29 am_home = os.path.join(noop_home, "../nexus-am") 30 dramsim3_home = os.path.join(noop_home, "../DRAMsim3") 31 rvtest_home = os.path.join(noop_home, "../riscv-tests") 32 33 def __init__(self, args): 34 # all path environment variables that should be set 35 all_path = [ 36 # (python argument, environment variable, default, target function) 37 (None, "NOOP_HOME", self.noop_home, self.set_noop_home), 38 (args.nemu, "NEMU_HOME", self.nemu_home, self.set_nemu_home), 39 (args.am, "AM_HOME", self.am_home, self.set_am_home), 40 (args.dramsim3, "DRAMSIM3_HOME", self.dramsim3_home, self.set_dramsim3_home), 41 (args.rvtest, "RVTEST_HOME", self.rvtest_home, self.set_rvtest_home), 42 ] 43 for (arg_in, env, default, set_func) in all_path: 44 set_func(self.__extract_path(arg_in, env, default)) 45 # Chisel arguments 46 self.disable_log = args.disable_log 47 self.dual_core = args.dual_core 48 # Makefile arguments 49 self.threads = args.threads 50 self.with_dramsim3 = 1 if args.with_dramsim3 else None 51 self.trace = 1 if args.trace else None 52 # emu arguments 53 self.max_instr = args.max_instr 54 self.numa = args.numa 55 56 def get_env_variables(self): 57 all_env = { 58 "NOOP_HOME" : self.noop_home, 59 "NEMU_HOME" : self.nemu_home, 60 "AM_HOME" : self.am_home, 61 "DRAMSIM3_HOME": self.dramsim3_home 62 } 63 return all_env 64 65 def get_chisel_args(self, prefix=None): 66 chisel_args = [ 67 (self.disable_log, "disable-log"), 68 (self.dual_core, "dual-core") 69 ] 70 args = map(lambda x: x[1], filter(lambda arg: arg[0], chisel_args)) 71 if prefix is not None: 72 args = map(lambda x: prefix + x, args) 73 return args 74 75 def get_makefile_args(self): 76 makefile_args = [ 77 (self.threads, "EMU_THREADS"), 78 (self.with_dramsim3, "WITH_DRAMSIM3"), 79 (self.trace, "EMU_TRACE") 80 ] 81 args = filter(lambda arg: arg[0] is not None, makefile_args) 82 return args 83 84 def get_emu_args(self): 85 emu_args = [ 86 (self.max_instr, "max-instr") 87 ] 88 args = filter(lambda arg: arg[0] is not None, emu_args) 89 return args 90 91 def show(self): 92 print("Extra environment variables:") 93 env = self.get_env_variables() 94 for env_name in env: 95 print(f"{env_name}: {env[env_name]}") 96 print() 97 print("Chisel arguments:") 98 print(" ".join(self.get_chisel_args())) 99 print() 100 print("Makefile arguments:") 101 for val, name in self.get_makefile_args(): 102 print(f"{name}={val}") 103 print() 104 print("emu arguments:") 105 for val, name in self.get_emu_args(): 106 print(f"--{name} {val}") 107 print() 108 109 def __extract_path(self, path, env=None, default=None): 110 if path is None and env is not None: 111 path = os.getenv(env) 112 if path is None and default is not None: 113 path = default 114 path = os.path.realpath(path) 115 return path 116 117 def set_noop_home(self, path): 118 self.noop_home = path 119 120 def set_nemu_home(self, path): 121 self.nemu_home = path 122 123 def set_am_home(self, path): 124 self.am_home = path 125 126 def set_dramsim3_home(self, path): 127 self.dramsim3_home = path 128 129 def set_rvtest_home(self, path): 130 self.rvtest_home = path 131 132# XiangShan environment 133class XiangShan(object): 134 def __init__(self, args): 135 self.args = XSArgs(args) 136 137 def show(self): 138 self.args.show() 139 140 def generate_verilog(self): 141 print("Generating XiangShan verilog with the following configurations:") 142 self.show() 143 sim_args = " ".join(self.args.get_chisel_args(prefix="--")) 144 make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args())) 145 return_code = self.__exec_cmd(f'make -C $NOOP_HOME verilog SIM_ARGS="{sim_args}" {make_args}') 146 return return_code 147 148 def build_emu(self): 149 print("Building XiangShan emu with the following configurations:") 150 self.show() 151 sim_args = " ".join(self.args.get_chisel_args(prefix="--")) 152 make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args())) 153 return_code = self.__exec_cmd(f'make -C $NOOP_HOME ./build/emu -j200 SIM_ARGS="{sim_args}" {make_args}') 154 return return_code 155 156 def run_emu(self, workload): 157 print("Running XiangShan emu with the following configurations:") 158 self.show() 159 emu_args = " ".join(map(lambda arg: f"--{arg[1]} {arg[0]}", self.args.get_emu_args())) 160 print("workload:", workload) 161 numa_args = f"numactl -m 1 -C 64-{64+self.args.threads-1}" if self.args.numa else "" 162 return_code = self.__exec_cmd(f'{numa_args} $NOOP_HOME/build/emu -i {workload} {emu_args}') 163 return return_code 164 165 def run(self, args): 166 if args.ci is not None: 167 return self.run_ci(args.ci) 168 actions = [ 169 (args.generate, lambda _ : self.generate_verilog()), 170 (args.build, lambda _ : self.build_emu()), 171 (args.workload, lambda args: self.run_emu(args.workload)) 172 ] 173 valid_actions = map(lambda act: act[1], filter(lambda act: act[0], actions)) 174 for i, action in enumerate(valid_actions): 175 print(f"Action {i}:") 176 ret = action(args) 177 if ret: 178 return ret 179 return 0 180 181 def __exec_cmd(self, cmd): 182 env = dict(os.environ) 183 env.update(self.args.get_env_variables()) 184 print("subprocess call cmd:", cmd) 185 return_code = subprocess.call(cmd, shell=True, env=env) 186 return return_code 187 188 def __get_ci_cputest(self, name=None): 189 base_dir = os.path.join(self.args.am_home, "tests/cputest/build") 190 cputest = os.listdir(base_dir) 191 cputest = filter(lambda x: x.endswith(".bin"), cputest) 192 cputest = map(lambda x: os.path.join(base_dir, x), cputest) 193 return cputest 194 195 def __get_ci_rvtest(self, name=None): 196 base_dir = os.path.join(self.args.rvtest_home, "isa/build") 197 riscv_tests = os.listdir(base_dir) 198 riscv_tests = filter(lambda x: x.endswith(".bin"), riscv_tests) 199 all_rv_tests = ["rv64ui", "rv64um", "rv64ua", "rv64uf", "rv64ud"] 200 riscv_tests = filter(lambda x: x[:6] in all_rv_tests, riscv_tests) 201 riscv_tests = map(lambda x: os.path.join(base_dir, x), riscv_tests) 202 return riscv_tests 203 204 def __am_apps_path(self, bench): 205 filename = f"{bench}-riscv64-noop.bin" 206 return [os.path.join(self.args.am_home, "apps", bench, "build", filename)] 207 208 def __get_ci_workloads(self, name): 209 workloads = { 210 "linux-hello": "bbl.bin", 211 "povray": "_3400001000_.gz", 212 "mcf": "_2550001000_.gz", 213 "xalancbmk": "_6600001000_.gz", 214 "gcc": "_1250001000_.gz", 215 "namd": "_4850001000_.gz", 216 "milc": "_4150001000_.gz", 217 "lbm": "_7550001000_.gz", 218 "gromacs": "_3150001000_.gz" 219 } 220 return [os.path.join("/home/ci-runner/xsenv/workloads", name, workloads[name])] 221 222 def run_ci(self, test): 223 all_tests = { 224 "cputest": self.__get_ci_cputest, 225 "riscv-tests": self.__get_ci_rvtest, 226 "microbench": self.__am_apps_path, 227 "coremark": self.__am_apps_path 228 } 229 for target in all_tests.get(test, self.__get_ci_workloads)(test): 230 print(target) 231 ret = self.run_emu(target) 232 if ret: 233 return ret 234 return 0 235 236if __name__ == "__main__": 237 parser = argparse.ArgumentParser(description='Python wrapper for XiangShan') 238 parser.add_argument('workload', nargs='?', type=str, default="", 239 help='input workload file in binary format') 240 # actions 241 parser.add_argument('--build', action='store_true', help='build XS emu') 242 parser.add_argument('--generate', action='store_true', help='generate XS verilog') 243 parser.add_argument('--ci', nargs='?', type=str, const="", help='run CI tests') 244 # environment variables 245 parser.add_argument('--nemu', nargs='?', type=str, help='path to nemu') 246 parser.add_argument('--am', nargs='?', type=str, help='path to nexus-am') 247 parser.add_argument('--dramsim3', nargs='?', type=str, help='path to dramsim3') 248 parser.add_argument('--rvtest', nargs='?', type=str, help='path to riscv-tests') 249 # chisel arguments 250 parser.add_argument('--disable-log', action='store_true', help='disable log') 251 parser.add_argument('--dual-core', action='store_true', help='dual core') 252 # makefile arguments 253 parser.add_argument('--with-dramsim3', action='store_true', help='enable dramsim3') 254 parser.add_argument('--threads', nargs='?', type=int, help='number of emu threads') 255 parser.add_argument('--trace', action='store_true', help='enable waveform') 256 # emu arguments 257 parser.add_argument('--numa', action='store_true', help='use numactl') 258 parser.add_argument('--max-instr', nargs='?', type=int, help='max instr') 259 260 args = parser.parse_args() 261 262 xs = XiangShan(args) 263 ret = xs.run(args) 264 265 sys.exit(ret) 266