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 json 21import os 22import random 23import signal 24import subprocess 25import sys 26import time 27 28import psutil 29 30 31def load_all_gcpt(gcpt_path, json_path): 32 all_gcpt = [] 33 with open(json_path) as f: 34 data = json.load(f) 35 for benchspec in data: 36 for point in data[benchspec]: 37 weight = data[benchspec][point] 38 gcpt = os.path.join(gcpt_path, "_".join([benchspec, point, weight])) 39 bin_dir = os.path.join(gcpt, "0") 40 bin_file = list(os.listdir(bin_dir)) 41 assert(len(bin_file) == 1) 42 bin_path = os.path.join(bin_dir, bin_file[0]) 43 assert(os.path.isfile(bin_path)) 44 all_gcpt.append(bin_path) 45 return all_gcpt 46 47class XSArgs(object): 48 script_path = os.path.realpath(__file__) 49 # default path to the repositories 50 noop_home = os.path.join(os.path.dirname(script_path), "..") 51 nemu_home = os.path.join(noop_home, "../NEMU") 52 am_home = os.path.join(noop_home, "../nexus-am") 53 dramsim3_home = os.path.join(noop_home, "../DRAMsim3") 54 rvtest_home = os.path.join(noop_home, "../riscv-tests") 55 default_wave_home = os.path.join(noop_home, "build") 56 wave_home = default_wave_home 57 58 def __init__(self, args): 59 # all path environment variables that should be set 60 all_path = [ 61 # (python argument, environment variable, default, target function) 62 (None, "NOOP_HOME", self.noop_home, self.set_noop_home), 63 (args.nemu, "NEMU_HOME", self.nemu_home, self.set_nemu_home), 64 (args.am, "AM_HOME", self.am_home, self.set_am_home), 65 (args.dramsim3, "DRAMSIM3_HOME", self.dramsim3_home, self.set_dramsim3_home), 66 (args.rvtest, "RVTEST_HOME", self.rvtest_home, self.set_rvtest_home), 67 ] 68 for (arg_in, env, default, set_func) in all_path: 69 set_func(self.__extract_path(arg_in, env, default)) 70 # Chisel arguments 71 self.enable_log = args.enable_log 72 self.num_cores = args.num_cores 73 # Makefile arguments 74 self.threads = args.threads 75 self.with_dramsim3 = 1 if args.with_dramsim3 else None 76 self.is_release = 1 if args.release else None 77 self.is_spike = "spike" if args.spike else None 78 self.trace = 1 if args.trace or not args.disable_fork and not args.trace_fst else None 79 self.trace_fst = "fst" if args.trace_fst else None 80 self.config = args.config 81 # emu arguments 82 self.max_instr = args.max_instr 83 self.ram_size = args.ram_size 84 self.seed = random.randint(0, 9999) 85 self.numa = args.numa 86 self.diff = args.diff 87 if args.spike and "nemu" in args.diff: 88 self.diff = self.diff.replace("nemu-interpreter", "spike") 89 self.fork = not args.disable_fork 90 self.disable_diff = args.no_diff 91 # wave dump path 92 if args.wave_dump is not None: 93 self.set_wave_home(args.wave_dump) 94 else: 95 self.set_wave_home(self.default_wave_home) 96 97 def get_env_variables(self): 98 all_env = { 99 "NOOP_HOME" : self.noop_home, 100 "NEMU_HOME" : self.nemu_home, 101 "WAVE_HOME" : self.wave_home, 102 "AM_HOME" : self.am_home, 103 "DRAMSIM3_HOME": self.dramsim3_home, 104 "MODULEPATH": "/usr/share/Modules/modulefiles:/etc/modulefiles" 105 } 106 return all_env 107 108 def get_chisel_args(self, prefix=None): 109 chisel_args = [ 110 (self.enable_log, "enable-log") 111 ] 112 args = map(lambda x: x[1], filter(lambda arg: arg[0], chisel_args)) 113 if prefix is not None: 114 args = map(lambda x: prefix + x, args) 115 return args 116 117 def get_makefile_args(self): 118 makefile_args = [ 119 (self.threads, "EMU_THREADS"), 120 (self.with_dramsim3, "WITH_DRAMSIM3"), 121 (self.is_release, "RELEASE"), 122 (self.is_spike, "REF"), 123 (self.trace, "EMU_TRACE"), 124 (self.trace_fst, "EMU_TRACE"), 125 (self.config, "CONFIG"), 126 (self.num_cores, "NUM_CORES") 127 ] 128 args = filter(lambda arg: arg[0] is not None, makefile_args) 129 return args 130 131 def get_emu_args(self): 132 emu_args = [ 133 (self.max_instr, "max-instr"), 134 (self.diff, "diff"), 135 (self.seed, "seed"), 136 (self.ram_size, "ram-size"), 137 ] 138 args = filter(lambda arg: arg[0] is not None, emu_args) 139 return args 140 141 def show(self): 142 print("Extra environment variables:") 143 env = self.get_env_variables() 144 for env_name in env: 145 print(f"{env_name}: {env[env_name]}") 146 print() 147 print("Chisel arguments:") 148 print(" ".join(self.get_chisel_args())) 149 print() 150 print("Makefile arguments:") 151 for val, name in self.get_makefile_args(): 152 print(f"{name}={val}") 153 print() 154 print("emu arguments:") 155 for val, name in self.get_emu_args(): 156 print(f"--{name} {val}") 157 print() 158 159 def __extract_path(self, path, env=None, default=None): 160 if path is None and env is not None: 161 path = os.getenv(env) 162 if path is None and default is not None: 163 path = default 164 path = os.path.realpath(path) 165 return path 166 167 def set_noop_home(self, path): 168 self.noop_home = path 169 170 def set_nemu_home(self, path): 171 self.nemu_home = path 172 173 def set_am_home(self, path): 174 self.am_home = path 175 176 def set_dramsim3_home(self, path): 177 self.dramsim3_home = path 178 179 def set_rvtest_home(self, path): 180 self.rvtest_home = path 181 182 def set_wave_home(self, path): 183 print(f"set wave home to {path}") 184 self.wave_home = path 185 186# XiangShan environment 187class XiangShan(object): 188 def __init__(self, args): 189 self.args = XSArgs(args) 190 self.timeout = args.timeout 191 192 def show(self): 193 self.args.show() 194 195 def make_clean(self): 196 print("Clean up CI workspace") 197 self.show() 198 return_code = self.__exec_cmd(f'make -C $NOOP_HOME clean') 199 return return_code 200 201 def generate_verilog(self): 202 print("Generating XiangShan verilog with the following configurations:") 203 self.show() 204 sim_args = " ".join(self.args.get_chisel_args(prefix="--")) 205 make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args())) 206 return_code = self.__exec_cmd(f'make -C $NOOP_HOME verilog SIM_ARGS="{sim_args}" {make_args}') 207 return return_code 208 209 def generate_sim_verilog(self): 210 print("Generating XiangShan sim-verilog with the following configurations:") 211 self.show() 212 sim_args = " ".join(self.args.get_chisel_args(prefix="--")) 213 make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args())) 214 return_code = self.__exec_cmd(f'make -C $NOOP_HOME sim-verilog SIM_ARGS="{sim_args}" {make_args}') 215 return return_code 216 217 def build_emu(self): 218 print("Building XiangShan emu with the following configurations:") 219 self.show() 220 sim_args = " ".join(self.args.get_chisel_args(prefix="--")) 221 make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args())) 222 return_code = self.__exec_cmd(f'make -C $NOOP_HOME emu -j200 SIM_ARGS="{sim_args}" {make_args}') 223 return return_code 224 225 def build_simv(self): 226 print("Building XiangShan simv with the following configurations") 227 self.show() 228 make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args())) 229 # TODO: make the following commands grouped as unseen scripts 230 return_code = self.__exec_cmd(f'\ 231 eval `/usr/bin/modulecmd zsh load license`;\ 232 eval `/usr/bin/modulecmd zsh load synopsys/vcs/Q-2020.03-SP2`;\ 233 eval `/usr/bin/modulecmd zsh load synopsys/verdi/S-2021.09-SP1`;\ 234 VERDI_HOME=/nfs/tools/synopsys/verdi/S-2021.09-SP1 \ 235 make -C $NOOP_HOME simv {make_args} CONSIDER_FSDB=1') # set CONSIDER_FSDB for compatibility 236 return return_code 237 238 def run_emu(self, workload): 239 print("Running XiangShan emu with the following configurations:") 240 self.show() 241 emu_args = " ".join(map(lambda arg: f"--{arg[1]} {arg[0]}", self.args.get_emu_args())) 242 print("workload:", workload) 243 numa_args = "" 244 if self.args.numa: 245 numa_info = get_free_cores(self.args.threads) 246 numa_args = f"numactl -m {numa_info[0]} -C {numa_info[1]}-{numa_info[2]}" 247 fork_args = "--enable-fork" if self.args.fork else "" 248 diff_args = "--no-diff" if self.args.disable_diff else "" 249 return_code = self.__exec_cmd(f'{numa_args} $NOOP_HOME/build/emu -i {workload} {emu_args} {fork_args} {diff_args}') 250 return return_code 251 252 def run_simv(self, workload): 253 print("Running XiangShan simv with the following configurations:") 254 self.show() 255 diff_args = "$NOOP_HOME/"+ args.diff 256 return_code = self.__exec_cmd(f'$NOOP_HOME/difftest/simv +workload={workload} +diff={diff_args}') 257 return return_code 258 259 def run(self, args): 260 if args.ci is not None: 261 return self.run_ci(args.ci) 262 if args.ci_vcs is not None: 263 return self.run_ci_vcs(args.ci_vcs) 264 actions = [ 265 (args.generate, lambda _ : self.generate_verilog()), 266 (args.vcs_gen, lambda _ : self.generate_sim_verilog()), 267 (args.build, lambda _ : self.build_emu()), 268 (args.vcs_build, lambda _ : self.build_simv()), 269 (args.workload, lambda args: self.run_emu(args.workload)), 270 (args.clean, lambda _ : self.make_clean()) 271 ] 272 valid_actions = map(lambda act: act[1], filter(lambda act: act[0], actions)) 273 for i, action in enumerate(valid_actions): 274 print(f"Action {i}:") 275 ret = action(args) 276 if ret: 277 return ret 278 return 0 279 280 def __exec_cmd(self, cmd): 281 env = dict(os.environ) 282 env.update(self.args.get_env_variables()) 283 print("subprocess call cmd:", cmd) 284 start = time.time() 285 proc = subprocess.Popen(cmd, shell=True, env=env, preexec_fn=os.setsid) 286 try: 287 return_code = proc.wait(self.timeout) 288 end = time.time() 289 print(f"Elapsed time: {end - start} seconds") 290 return return_code 291 except (KeyboardInterrupt, subprocess.TimeoutExpired): 292 os.killpg(os.getpgid(proc.pid), signal.SIGINT) 293 print(f"KeyboardInterrupt or TimeoutExpired.") 294 return 0 295 296 def __get_ci_cputest(self, name=None): 297 base_dir = os.path.join(self.args.am_home, "tests/cputest/build") 298 cputest = os.listdir(base_dir) 299 cputest = filter(lambda x: x.endswith(".bin"), cputest) 300 cputest = map(lambda x: os.path.join(base_dir, x), cputest) 301 return cputest 302 303 def __get_ci_rvtest(self, name=None): 304 base_dir = os.path.join(self.args.rvtest_home, "isa/build") 305 riscv_tests = os.listdir(base_dir) 306 riscv_tests = filter(lambda x: x.endswith(".bin"), riscv_tests) 307 all_rv_tests = ["rv64ui", "rv64um", "rv64ua", "rv64uf", "rv64ud"] 308 riscv_tests = filter(lambda x: x[:6] in all_rv_tests, riscv_tests) 309 riscv_tests = map(lambda x: os.path.join(base_dir, x), riscv_tests) 310 return riscv_tests 311 312 def __get_ci_misc(self, name=None): 313 base_dir = "/nfs/home/share/ci-workloads" 314 workloads = [ 315 "bitmanip/bitMisc.bin", 316 "crypto/crypto-riscv64-noop.bin", 317 "coremark_rv64gc_o2/coremark-riscv64-xs.bin", 318 "coremark_rv64gc_o3/coremark-riscv64-xs.bin", 319 "coremark_rv64gcb_o3/coremark-riscv64-xs.bin", 320 "ext_intr/amtest-riscv64-xs.bin", 321 "cache-alias/aliastest-riscv64-xs.bin", 322 "Svinval/rv64mi-p-svinval.bin", 323 "pmp/pmp.riscv.bin", 324 "pmp/pmp-am/amtest-riscv64-xs.bin", 325 "pmp/hugepage-pmp-atom/amtest-riscv64-xs.bin", 326 "asid/asid.bin", 327 "isa_misc/xret_clear_mprv.bin", 328 "isa_misc/satp_ppn.bin", 329 "cache-management/softprefetchtest-riscv64-xs.bin" 330 ] 331 misc_tests = map(lambda x: os.path.join(base_dir, x), workloads) 332 return misc_tests 333 334 def __get_ci_mc(self, name=None): 335 base_dir = "/nfs/home/share/ci-workloads" 336 workloads = [ 337 "dualcoretest/ldvio-riscv64-xs.bin" 338 ] 339 mc_tests = map(lambda x: os.path.join(base_dir, x), workloads) 340 return mc_tests 341 342 def __get_ci_nodiff(self, name=None): 343 base_dir = "/nfs/home/share/ci-workloads" 344 workloads = [ 345 "cache-management/cacheoptest-riscv64-xs.bin" 346 ] 347 tests = map(lambda x: os.path.join(base_dir, x), workloads) 348 return tests 349 350 def __am_apps_path(self, bench): 351 filename = f"{bench}-riscv64-noop.bin" 352 return [os.path.join(self.args.am_home, "apps", bench, "build", filename)] 353 354 def __get_ci_workloads(self, name): 355 workloads = { 356 "linux-hello": "bbl.bin", 357 "linux-hello-smp": "bbl.bin", 358 "povray": "_700480000000_.gz", 359 "mcf": "_17520000000_.gz", 360 "xalancbmk": "_266100000000_.gz", 361 "gcc": "_39720000000_.gz", 362 "namd": "_434640000000_.gz", 363 "milc": "_103620000000_.gz", 364 "lbm": "_140840000000_.gz", 365 "gromacs": "_275480000000_.gz", 366 "wrf": "_1916220000000_.gz", 367 "astar": "_122060000000_.gz" 368 } 369 if name in workloads: 370 return [os.path.join("/nfs/home/share/ci-workloads", name, workloads[name])] 371 # select a random SPEC checkpoint 372 assert(name == "random") 373 all_cpt = [ 374 "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gcb_o2_20m/take_cpt", 375 "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gcb_o3_20m/take_cpt", 376 "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gc_o2_20m/take_cpt", 377 "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gc_o2_50m/take_cpt", 378 "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gcb_o2_20m/take_cpt", 379 "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gcb_o3_20m/take_cpt", 380 "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gc_o2_50m/take_cpt", 381 "/nfs-nvme/home/share/checkpoints_profiles/spec17_speed_rv64gcb_o3_20m/take_cpt" 382 ] 383 all_json = [ 384 "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gcb_o2_20m/json/simpoint_summary.json", 385 "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gcb_o3_20m/simpoint_summary.json", 386 "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gc_o2_20m/simpoint_summary.json", 387 "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gc_o2_50m/simpoint_summary.json", 388 "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gcb_o2_20m/simpoint_summary.json", 389 "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gcb_o3_20m/simpoint_summary.json", 390 "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gc_o2_50m/simpoint_summary.json", 391 "/nfs-nvme/home/share/checkpoints_profiles/spec17_speed_rv64gcb_o3_20m/simpoint_summary.json" 392 ] 393 assert(len(all_cpt) == len(all_json)) 394 cpt_path, json_path = random.choice(list(zip(all_cpt, all_json))) 395 all_gcpt = load_all_gcpt(cpt_path, json_path) 396 return [random.choice(all_gcpt)] 397 398 def run_ci(self, test): 399 all_tests = { 400 "cputest": self.__get_ci_cputest, 401 "riscv-tests": self.__get_ci_rvtest, 402 "misc-tests": self.__get_ci_misc, 403 "mc-tests": self.__get_ci_mc, 404 "nodiff-tests": self.__get_ci_nodiff, 405 "microbench": self.__am_apps_path, 406 "coremark": self.__am_apps_path 407 } 408 for target in all_tests.get(test, self.__get_ci_workloads)(test): 409 print(target) 410 ret = self.run_emu(target) 411 if ret: 412 if self.args.default_wave_home != self.args.wave_home: 413 print("copy wave file to " + self.args.wave_home) 414 self.__exec_cmd(f"cp $NOOP_HOME/build/*.vcd $WAVE_HOME") 415 self.__exec_cmd(f"cp $NOOP_HOME/build/emu $WAVE_HOME") 416 self.__exec_cmd(f"cp $NOOP_HOME/build/SimTop.v $WAVE_HOME") 417 return ret 418 return 0 419 420 def run_ci_vcs(self, test): 421 all_tests = { 422 "cputest": self.__get_ci_cputest, 423 "riscv-tests": self.__get_ci_rvtest, 424 "misc-tests": self.__get_ci_misc, 425 "mc-tests": self.__get_ci_mc, 426 "nodiff-tests": self.__get_ci_nodiff, 427 "microbench": self.__am_apps_path, 428 "coremark": self.__am_apps_path 429 } 430 for target in all_tests.get(test, self.__get_ci_workloads)(test): 431 print(target) 432 ret = self.run_simv(target) 433 if ret: 434 if self.args.default_wave_home != self.args.wave_home: 435 print("copy wave file to " + self.args.wave_home) 436 self.__exec_cmd(f"cp $NOOP_HOME/build/*.vcd $WAVE_HOME") 437 self.__exec_cmd(f"cp $NOOP_HOME/build/emu $WAVE_HOME") 438 self.__exec_cmd(f"cp $NOOP_HOME/build/SimTop.v $WAVE_HOME") 439 return ret 440 return 0 441 442def get_free_cores(n): 443 while True: 444 # To avoid potential conflicts, we allow CI to use SMT. 445 num_logical_core = psutil.cpu_count(logical=False) 446 core_usage = psutil.cpu_percent(interval=1, percpu=True) 447 num_window = num_logical_core // n 448 for i in range(num_window): 449 window_usage = core_usage[i * n : i * n + n] 450 if sum(window_usage) < 30 * n and True not in map(lambda x: x > 90, window_usage): 451 return (((i * n) % 128)// 64, i * n, i * n + n - 1) 452 print(f"No free {n} cores found. CPU usage: {core_usage}\n") 453 454if __name__ == "__main__": 455 parser = argparse.ArgumentParser(description='Python wrapper for XiangShan') 456 parser.add_argument('workload', nargs='?', type=str, default="", 457 help='input workload file in binary format') 458 # actions 459 parser.add_argument('--build', action='store_true', help='build XS emu') 460 parser.add_argument('--generate', action='store_true', help='generate XS verilog') 461 parser.add_argument('--vcs-gen', action='store_true', help='generate XS sim verilog for vcs') 462 parser.add_argument('--vcs-build', action='store_true', help='build XS simv') 463 parser.add_argument('--ci', nargs='?', type=str, const="", help='run CI tests') 464 parser.add_argument('--ci-vcs', nargs='?', type=str, const="", help='run CI tests on simv') 465 parser.add_argument('--clean', action='store_true', help='clean up XiangShan CI workspace') 466 parser.add_argument('--timeout', nargs='?', type=int, default=None, help='timeout (in seconds)') 467 # environment variables 468 parser.add_argument('--nemu', nargs='?', type=str, help='path to nemu') 469 parser.add_argument('--am', nargs='?', type=str, help='path to nexus-am') 470 parser.add_argument('--dramsim3', nargs='?', type=str, help='path to dramsim3') 471 parser.add_argument('--rvtest', nargs='?', type=str, help='path to riscv-tests') 472 parser.add_argument('--wave-dump', nargs='?', type=str , help='path to dump wave') 473 # chisel arguments 474 parser.add_argument('--enable-log', action='store_true', help='enable log') 475 parser.add_argument('--num-cores', type=int, help='number of cores') 476 # makefile arguments 477 parser.add_argument('--release', action='store_true', help='enable release') 478 parser.add_argument('--spike', action='store_true', help='enable spike diff') 479 parser.add_argument('--with-dramsim3', action='store_true', help='enable dramsim3') 480 parser.add_argument('--threads', nargs='?', type=int, help='number of emu threads') 481 parser.add_argument('--trace', action='store_true', help='enable vcd waveform') 482 parser.add_argument('--trace-fst', action='store_true', help='enable fst waveform') 483 parser.add_argument('--config', nargs='?', type=str, help='config') 484 # emu arguments 485 parser.add_argument('--numa', action='store_true', help='use numactl') 486 parser.add_argument('--diff', nargs='?', default="./ready-to-run/riscv64-nemu-interpreter-so", type=str, help='nemu so') 487 parser.add_argument('--max-instr', nargs='?', type=int, help='max instr') 488 parser.add_argument('--disable-fork', action='store_true', help='disable lightSSS') 489 parser.add_argument('--no-diff', action='store_true', help='disable difftest') 490 parser.add_argument('--ram-size', nargs='?', type=str, help='manually set simulation memory size (8GB by default)') 491 492 args = parser.parse_args() 493 494 xs = XiangShan(args) 495 ret = xs.run(args) 496 497 sys.exit(ret) 498