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