xref: /XiangShan/scripts/xiangshan.py (revision 6639e9a467468f4e1b05a25a5de4500772aedeb1)
1#***************************************************************************************
2# Copyright (c) 2024 Beijing Institute of Open Source Chip (BOSC)
3# Copyright (c) 2020-2024 Institute of Computing Technology, Chinese Academy of Sciences
4# Copyright (c) 2020-2021 Peng Cheng Laboratory
5#
6# XiangShan is licensed under Mulan PSL v2.
7# You can use this software according to the terms and conditions of the Mulan PSL v2.
8# You may obtain a copy of Mulan PSL v2 at:
9#          http://license.coscl.org.cn/MulanPSL2
10#
11# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
12# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
13# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
14#
15# See the Mulan PSL v2 for more details.
16#***************************************************************************************
17
18# Simple version of xiangshan python wrapper
19
20import argparse
21import json
22import os
23import random
24import signal
25import subprocess
26import sys
27import time
28import shlex
29import psutil
30import re
31
32def load_all_gcpt(gcpt_path, json_path):
33    all_gcpt = []
34    with open(json_path) as f:
35        data = json.load(f)
36    for benchspec in data:
37        for point in data[benchspec]:
38            weight = data[benchspec][point]
39            gcpt = os.path.join(gcpt_path, "_".join([benchspec, point, weight]))
40            bin_dir = os.path.join(gcpt, "0")
41            bin_file = list(os.listdir(bin_dir))
42            assert(len(bin_file) == 1)
43            bin_path = os.path.join(bin_dir, bin_file[0])
44            assert(os.path.isfile(bin_path))
45            all_gcpt.append(bin_path)
46    return all_gcpt
47
48class XSArgs(object):
49    script_path = os.path.realpath(__file__)
50    # default path to the repositories
51    noop_home = os.path.join(os.path.dirname(script_path), "..")
52    nemu_home = os.path.join(noop_home, "../NEMU")
53    am_home = os.path.join(noop_home, "../nexus-am")
54    dramsim3_home = os.path.join(noop_home, "../DRAMsim3")
55    rvtest_home = os.path.join(noop_home, "../riscv-tests")
56    default_wave_home = os.path.join(noop_home, "build")
57    wave_home   = default_wave_home
58
59    def __init__(self, args):
60        # all path environment variables that should be set
61        all_path = [
62            # (python argument, environment variable, default, target function)
63            (None, "NOOP_HOME", self.noop_home, self.set_noop_home),
64            (args.nemu, "NEMU_HOME", self.nemu_home, self.set_nemu_home),
65            (args.am, "AM_HOME", self.am_home, self.set_am_home),
66            (args.dramsim3, "DRAMSIM3_HOME", self.dramsim3_home, self.set_dramsim3_home),
67            (args.rvtest, "RVTEST_HOME", self.rvtest_home, self.set_rvtest_home),
68        ]
69        for (arg_in, env, default, set_func) in all_path:
70            set_func(self.__extract_path(arg_in, env, default))
71        # Chisel arguments
72        self.enable_log = args.enable_log
73        self.num_cores = args.num_cores
74        # Makefile arguments
75        self.threads = args.threads
76        self.with_dramsim3 = 1 if args.with_dramsim3 else None
77        self.is_release = 1 if args.release else None
78        self.is_spike = "Spike" if args.spike else None
79        self.trace = 1 if args.trace or not args.disable_fork and not args.trace_fst else None
80        self.trace_fst = "fst" if args.trace_fst else None
81        self.config = args.config
82        self.emu_optimize = args.emu_optimize
83        self.xprop = 1 if args.xprop else None
84        self.with_chiseldb = 0 if args.no_db else 1
85        # emu arguments
86        self.max_instr = args.max_instr
87        self.ram_size = args.ram_size
88        self.seed = random.randint(0, 9999)
89        self.numa = args.numa
90        self.diff = args.diff
91        if args.spike and "nemu" in args.diff:
92            self.diff = self.diff.replace("nemu-interpreter", "spike")
93        self.fork = not args.disable_fork
94        self.disable_diff = args.no_diff
95        self.disable_db = args.no_db
96        self.gcpt_restore_bin = args.gcpt_restore_bin
97        self.pgo = args.pgo
98        self.pgo_max_cycle = args.pgo_max_cycle
99        self.pgo_emu_args = args.pgo_emu_args
100        self.llvm_profdata = args.llvm_profdata
101        # wave dump path
102        if args.wave_dump is not None:
103            self.set_wave_home(args.wave_dump)
104        else:
105            self.set_wave_home(self.default_wave_home)
106
107    def get_env_variables(self):
108        all_env = {
109            "NOOP_HOME"    : self.noop_home,
110            "NEMU_HOME"    : self.nemu_home,
111            "WAVE_HOME"    : self.wave_home,
112            "AM_HOME"      : self.am_home,
113            "DRAMSIM3_HOME": self.dramsim3_home,
114            "MODULEPATH": "/usr/share/Modules/modulefiles:/etc/modulefiles"
115        }
116        return all_env
117
118    def get_chisel_args(self, prefix=None):
119        chisel_args = [
120            (self.enable_log, "enable-log")
121        ]
122        args = map(lambda x: x[1], filter(lambda arg: arg[0], chisel_args))
123        if prefix is not None:
124            args = map(lambda x: prefix + x, args)
125        return args
126
127    def get_makefile_args(self):
128        makefile_args = [
129            (self.threads,       "EMU_THREADS"),
130            (self.with_dramsim3, "WITH_DRAMSIM3"),
131            (self.is_release,    "RELEASE"),
132            (self.is_spike,      "REF"),
133            (self.trace,         "EMU_TRACE"),
134            (self.trace_fst,     "EMU_TRACE"),
135            (self.config,        "CONFIG"),
136            (self.num_cores,     "NUM_CORES"),
137            (self.emu_optimize,  "EMU_OPTIMIZE"),
138            (self.xprop,         "ENABLE_XPROP"),
139            (self.with_chiseldb, "WITH_CHISELDB"),
140            (self.pgo,           "PGO_WORKLOAD"),
141            (self.pgo_max_cycle, "PGO_MAX_CYCLE"),
142            (self.pgo_emu_args,  "PGO_EMU_ARGS"),
143            (self.llvm_profdata, "LLVM_PROFDATA"),
144        ]
145        args = filter(lambda arg: arg[0] is not None, makefile_args)
146        args = [(shlex.quote(str(arg[0])), arg[1]) for arg in args] # shell escape
147        return args
148
149    def get_emu_args(self):
150        emu_args = [
151            (self.max_instr, "max-instr"),
152            (self.diff,      "diff"),
153            (self.seed,      "seed"),
154            (self.ram_size,  "ram-size"),
155        ]
156        args = filter(lambda arg: arg[0] is not None, emu_args)
157        return args
158
159    def show(self):
160        print("Extra environment variables:")
161        env = self.get_env_variables()
162        for env_name in env:
163            print(f"{env_name}: {env[env_name]}")
164        print()
165        print("Chisel arguments:")
166        print(" ".join(self.get_chisel_args()))
167        print()
168        print("Makefile arguments:")
169        for val, name in self.get_makefile_args():
170            print(f"{name}={val}")
171        print()
172        print("emu arguments:")
173        for val, name in self.get_emu_args():
174            print(f"--{name} {val}")
175        print()
176
177    def __extract_path(self, path, env=None, default=None):
178        if path is None and env is not None:
179            path = os.getenv(env)
180        if path is None and default is not None:
181            path = default
182        path = os.path.realpath(path)
183        return path
184
185    def set_noop_home(self, path):
186        self.noop_home = path
187
188    def set_nemu_home(self, path):
189        self.nemu_home = path
190
191    def set_am_home(self, path):
192        self.am_home = path
193
194    def set_dramsim3_home(self, path):
195        self.dramsim3_home = path
196
197    def set_rvtest_home(self, path):
198        self.rvtest_home = path
199
200    def set_wave_home(self, path):
201        print(f"set wave home to {path}")
202        self.wave_home = path
203
204# XiangShan environment
205class XiangShan(object):
206    def __init__(self, args):
207        self.args = XSArgs(args)
208        self.timeout = args.timeout
209
210    def show(self):
211        self.args.show()
212
213    def make_clean(self):
214        print("Clean up CI workspace")
215        self.show()
216        return_code = self.__exec_cmd(f'make -C $NOOP_HOME clean')
217        return return_code
218
219    def generate_verilog(self):
220        print("Generating XiangShan verilog with the following configurations:")
221        self.show()
222        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
223        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
224        return_code = self.__exec_cmd(f'make -C $NOOP_HOME verilog SIM_ARGS="{sim_args}" {make_args}')
225        return return_code
226
227    def generate_sim_verilog(self):
228        print("Generating XiangShan sim-verilog with the following configurations:")
229        self.show()
230        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
231        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
232        return_code = self.__exec_cmd(f'make -C $NOOP_HOME sim-verilog SIM_ARGS="{sim_args}" {make_args}')
233        return return_code
234
235    def build_emu(self):
236        print("Building XiangShan emu with the following configurations:")
237        self.show()
238        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
239        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
240        return_code = self.__exec_cmd(f'make -C $NOOP_HOME emu -j200 SIM_ARGS="{sim_args}" {make_args}')
241        return return_code
242
243    def build_simv(self):
244        print("Building XiangShan simv with the following configurations")
245        self.show()
246        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
247        # TODO: make the following commands grouped as unseen scripts
248        return_code = self.__exec_cmd(f'\
249            eval `/usr/bin/modulecmd zsh load license`;\
250            eval `/usr/bin/modulecmd zsh load synopsys/vcs/Q-2020.03-SP2`;\
251            eval `/usr/bin/modulecmd zsh load synopsys/verdi/S-2021.09-SP1`;\
252            VERDI_HOME=/nfs/tools/synopsys/verdi/S-2021.09-SP1 \
253            make -C $NOOP_HOME simv {make_args} CONSIDER_FSDB=1')  # set CONSIDER_FSDB for compatibility
254        return return_code
255
256    def run_emu(self, workload):
257        print("Running XiangShan emu with the following configurations:")
258        self.show()
259        emu_args = " ".join(map(lambda arg: f"--{arg[1]} {arg[0]}", self.args.get_emu_args()))
260        print("workload:", workload)
261        numa_args = ""
262        if self.args.numa:
263            numa_info = get_free_cores(self.args.threads)
264            numa_args = f"numactl -m {numa_info[0]} -C {numa_info[1]}-{numa_info[2]}"
265        fork_args = "--enable-fork -X 10" if self.args.fork else ""
266        diff_args = "--no-diff" if self.args.disable_diff else ""
267        chiseldb_args = "--dump-db" if not self.args.disable_db else ""
268        gcpt_restore_args = f"-r {self.args.gcpt_restore_bin}" if len(self.args.gcpt_restore_bin) != 0 else ""
269        return_code = self.__exec_cmd(f'ulimit -s {32 * 1024}; {numa_args} $NOOP_HOME/build/emu -i {workload} {emu_args} {fork_args} {diff_args} {chiseldb_args} {gcpt_restore_args}')
270        return return_code
271
272    def run_simv(self, workload):
273        print("Running XiangShan simv with the following configurations:")
274        self.show()
275        diff_args = "$NOOP_HOME/"+ args.diff
276        assert_args = "-assert finish_maxfail=30 -assert global_finish_maxfail=10000"
277        return_code = self.__exec_cmd(f'cd $NOOP_HOME/build && ./simv +workload={workload} +diff={diff_args} +dump-wave=fsdb {assert_args} | tee simv.log')
278        with open(f"{self.args.noop_home}/build/simv.log") as f:
279            content = f.read()
280            if "Offending" in content or "HIT GOOD TRAP" not in content:
281                return 1
282        return return_code
283
284    def run(self, args):
285        if args.ci is not None:
286            return self.run_ci(args.ci)
287        if args.ci_vcs is not None:
288            return self.run_ci_vcs(args.ci_vcs)
289        actions = [
290            (args.generate, lambda _ : self.generate_verilog()),
291            (args.vcs_gen, lambda _ : self.generate_sim_verilog()),
292            (args.build, lambda _ : self.build_emu()),
293            (args.vcs_build, lambda _ : self.build_simv()),
294            (args.workload, lambda args: self.run_emu(args.workload)),
295            (args.clean, lambda _ : self.make_clean())
296        ]
297        valid_actions = map(lambda act: act[1], filter(lambda act: act[0], actions))
298        for i, action in enumerate(valid_actions):
299            print(f"Action {i}:")
300            ret = action(args)
301            if ret:
302                return ret
303        return 0
304
305    def __exec_cmd(self, cmd):
306        env = dict(os.environ)
307        env.update(self.args.get_env_variables())
308        print("subprocess call cmd:", cmd)
309        start = time.time()
310        proc = subprocess.Popen(cmd, shell=True, env=env, preexec_fn=os.setsid)
311        try:
312            return_code = proc.wait(self.timeout)
313            end = time.time()
314            print(f"Elapsed time: {end - start} seconds")
315            return return_code
316        except (KeyboardInterrupt, subprocess.TimeoutExpired):
317            os.killpg(os.getpgid(proc.pid), signal.SIGINT)
318            print(f"KeyboardInterrupt or TimeoutExpired.")
319            return 0
320
321    def __get_ci_cputest(self, name=None):
322        # base_dir = os.path.join(self.args.am_home, "tests/cputest/build")
323        base_dir = "/nfs/home/share/ci-workloads/nexus-am-workloads/tests/cputest"
324        cputest = os.listdir(base_dir)
325        cputest = filter(lambda x: x.endswith(".bin"), cputest)
326        cputest = map(lambda x: os.path.join(base_dir, x), cputest)
327        return cputest
328
329    def __get_ci_rvtest(self, name=None):
330        base_dir = os.path.join(self.args.rvtest_home, "isa/build")
331        riscv_tests = os.listdir(base_dir)
332        riscv_tests = filter(lambda x: x.endswith(".bin"), riscv_tests)
333        all_rv_tests = ["rv64ui", "rv64um", "rv64ua", "rv64uf", "rv64ud", "rv64mi"]
334        riscv_tests = filter(lambda x: x[:6] in all_rv_tests, riscv_tests)
335        riscv_tests = map(lambda x: os.path.join(base_dir, x), riscv_tests)
336        return riscv_tests
337
338    def __get_ci_misc(self, name=None):
339        base_dir = "/nfs/home/share/ci-workloads"
340        workloads = [
341            "bitmanip/bitMisc.bin",
342            "crypto/crypto-riscv64-noop.bin",
343            # "coremark_rv64gc_o2/coremark-riscv64-xs.bin",
344            # "coremark_rv64gc_o3/coremark-riscv64-xs.bin",
345            # "coremark_rv64gcb_o3/coremark-riscv64-xs.bin",
346            "nexus-am-workloads/amtest/external_intr-riscv64-xs.bin",
347            "nexus-am-workloads/tests/aliastest/aliastest-riscv64-xs.bin",
348            "Svinval/rv64mi-p-svinval.bin",
349            "pmp/pmp.riscv.bin",
350            "nexus-am-workloads/amtest/pmp_test-riscv64-xs.bin",
351            "nexus-am-workloads/amtest/sv39_hp_atom_test-riscv64-xs.bin",
352            "asid/asid.bin",
353            "isa_misc/xret_clear_mprv.bin",
354            "isa_misc/satp_ppn.bin",
355            "cache-management/softprefetchtest-riscv64-xs.bin"
356        ]
357        misc_tests = map(lambda x: os.path.join(base_dir, x), workloads)
358        return misc_tests
359
360    def __get_ci_rvhtest(self, name=None):
361        base_dir = "/nfs/home/share/ci-workloads/H-extension-tests"
362        workloads = [
363            "riscv-hyp-tests/rvh_test.bin",
364            "xvisor_wboxtest/checkpoint.gz",
365        ]
366        rvh_tests = map(lambda x: os.path.join(base_dir, x), workloads)
367        return rvh_tests
368
369    def __get_ci_rvvbench(self, name=None):
370        base_dir = "/nfs/home/share/ci-workloads"
371        workloads = [
372            "rvv-bench/poly1305.bin",
373            "rvv-bench/mergelines.bin"
374        ]
375        rvvbench = map(lambda x: os.path.join(base_dir, x), workloads)
376        return rvvbench
377
378    def __get_ci_rvvtest(self, name=None):
379        base_dir = "/nfs/home/share/ci-workloads/V-extension-tests"
380        workloads = [
381            "rvv-test/vluxei32.v-0.bin",
382            "rvv-test/vlsseg4e32.v-0.bin",
383            "rvv-test/vlseg4e32.v-0.bin",
384            "rvv-test/vsetvl-0.bin",
385            "rvv-test/vsetivli-0.bin",
386            "rvv-test/vsuxei32.v-0.bin",
387            "rvv-test/vse16.v-0.bin",
388            "rvv-test/vsse16.v-1.bin",
389            "rvv-test/vlse32.v-0.bin",
390            "rvv-test/vsetvli-0.bin",
391            "rvv-test/vle16.v-0.bin",
392            "rvv-test/vle32.v-0.bin",
393            "rvv-test/vfsgnj.vv-0.bin",
394            "rvv-test/vfadd.vf-0.bin",
395            "rvv-test/vfsub.vf-0.bin",
396            "rvv-test/vslide1down.vx-0.bin"
397        ]
398        rvv_test = map(lambda x: os.path.join(base_dir, x), workloads)
399        return rvv_test
400
401    def __get_ci_F16test(self, name=None):
402        base_dir = "/nfs/home/share/ci-workloads/vector/F16-tests/build"
403        workloads = [
404            "rv64uzfhmin-p-fzfhmincvt.bin",
405            "rv64uzfh-p-fadd.bin",
406            "rv64uzfh-p-fclass.bin",
407            "rv64uzfh-p-fcmp.bin",
408            "rv64uzfh-p-fcvt.bin",
409            "rv64uzfh-p-fcvt_w.bin",
410            "rv64uzfh-p-fdiv.bin",
411            "rv64uzfh-p-fmadd.bin",
412            "rv64uzfh-p-fmin.bin",
413            "rv64uzfh-p-ldst.bin",
414            "rv64uzfh-p-move.bin",
415            "rv64uzfh-p-recoding.bin",
416            "rv64uzvfh-p-vfadd.bin",
417            "rv64uzvfh-p-vfclass.bin",
418            "rv64uzvfh-p-vfcvtfx.bin",
419            "rv64uzvfh-p-vfcvtfxu.bin",
420            "rv64uzvfh-p-vfcvtrxf.bin",
421            "rv64uzvfh-p-vfcvtrxuf.bin",
422            "rv64uzvfh-p-vfcvtxf.bin",
423            "rv64uzvfh-p-vfcvtxuf.bin",
424            "rv64uzvfh-p-vfdiv.bin",
425            "rv64uzvfh-p-vfdown.bin",
426            "rv64uzvfh-p-vfmacc.bin",
427            "rv64uzvfh-p-vfmadd.bin",
428            "rv64uzvfh-p-vfmax.bin",
429            "rv64uzvfh-p-vfmerge.bin",
430            "rv64uzvfh-p-vfmin.bin",
431            "rv64uzvfh-p-vfmsac.bin",
432            "rv64uzvfh-p-vfmsub.bin",
433            "rv64uzvfh-p-vfmul.bin",
434            "rv64uzvfh-p-vfmv.bin",
435            "rv64uzvfh-p-vfncvtff.bin",
436            "rv64uzvfh-p-vfncvtfx.bin",
437            "rv64uzvfh-p-vfncvtfxu.bin",
438            "rv64uzvfh-p-vfncvtrff.bin",
439            "rv64uzvfh-p-vfncvtrxf.bin",
440            "rv64uzvfh-p-vfncvtrxuf.bin",
441            "rv64uzvfh-p-vfncvtxf.bin",
442            "rv64uzvfh-p-vfncvtxuf.bin",
443            "rv64uzvfh-p-vfnmacc.bin",
444            "rv64uzvfh-p-vfnmadd.bin",
445            "rv64uzvfh-p-vfnmsac.bin",
446            "rv64uzvfh-p-vfnmsub.bin",
447            "rv64uzvfh-p-vfrdiv.bin",
448            "rv64uzvfh-p-vfrec7.bin",
449            "rv64uzvfh-p-vfredmax.bin",
450            "rv64uzvfh-p-vfredmin.bin",
451            "rv64uzvfh-p-vfredosum.bin",
452            "rv64uzvfh-p-vfredusum.bin",
453            "rv64uzvfh-p-vfrsqrt7.bin",
454            "rv64uzvfh-p-vfrsub.bin",
455            "rv64uzvfh-p-vfsgnj.bin",
456            "rv64uzvfh-p-vfsgnjn.bin",
457            "rv64uzvfh-p-vfsgnjx.bin",
458            "rv64uzvfh-p-vfsqrt.bin",
459            "rv64uzvfh-p-vfsub.bin",
460            "rv64uzvfh-p-vfup.bin",
461            "rv64uzvfh-p-vfwadd.bin",
462            "rv64uzvfh-p-vfwadd-w.bin",
463            "rv64uzvfh-p-vfwcvtff.bin",
464            "rv64uzvfh-p-vfwcvtfx.bin",
465            "rv64uzvfh-p-vfwcvtfxu.bin",
466            "rv64uzvfh-p-vfwcvtrxf.bin",
467            "rv64uzvfh-p-vfwcvtrxuf.bin",
468            "rv64uzvfh-p-vfwcvtxf.bin",
469            "rv64uzvfh-p-vfwcvtxuf.bin",
470            "rv64uzvfh-p-vfwmacc.bin",
471            "rv64uzvfh-p-vfwmsac.bin",
472            "rv64uzvfh-p-vfwmul.bin",
473            "rv64uzvfh-p-vfwnmacc.bin",
474            "rv64uzvfh-p-vfwnmsac.bin",
475            "rv64uzvfh-p-vfwredosum.bin",
476            "rv64uzvfh-p-vfwredusum.bin",
477            "rv64uzvfh-p-vfwsub.bin",
478            "rv64uzvfh-p-vfwsub-w.bin",
479            "rv64uzvfh-p-vmfeq.bin",
480            "rv64uzvfh-p-vmfge.bin",
481            "rv64uzvfh-p-vmfgt.bin",
482            "rv64uzvfh-p-vmfle.bin",
483            "rv64uzvfh-p-vmflt.bin",
484            "rv64uzvfh-p-vmfne.bin"
485        ]
486        f16_test = map(lambda x: os.path.join(base_dir, x), workloads)
487        return f16_test
488    def __get_ci_zcbtest(self, name=None):
489        base_dir = "/nfs/home/share/ci-workloads/zcb-test"
490        workloads = [
491            "zcb-test-riscv64-xs.bin"
492        ]
493        zcb_test = map(lambda x: os.path.join(base_dir, x), workloads)
494        return zcb_test
495
496    def __get_ci_mc(self, name=None):
497        base_dir = "/nfs/home/share/ci-workloads"
498        workloads = [
499            "nexus-am-workloads/tests/dualcoretest/ldvio-riscv64-xs.bin"
500        ]
501        mc_tests = map(lambda x: os.path.join(base_dir, x), workloads)
502        return mc_tests
503
504    def __get_ci_nodiff(self, name=None):
505        base_dir = "/nfs/home/share/ci-workloads"
506        workloads = [
507            "cache-management/cacheoptest-riscv64-xs.bin"
508        ]
509        tests = map(lambda x: os.path.join(base_dir, x), workloads)
510        return tests
511
512    def __am_apps_path(self, bench):
513        base_dir = '/nfs/home/share/ci-workloads/nexus-am-workloads/apps'
514        filename = f"{bench}-riscv64-xs.bin"
515        return [os.path.join(base_dir, bench, filename)]
516
517    def __get_ci_workloads(self, name):
518        workloads = {
519            "linux-hello": "bbl.bin",
520            "linux-hello-smp": "bbl.bin",
521            "linux-hello-opensbi": "fw_payload.bin",
522            "linux-hello-smp-opensbi": "fw_payload.bin",
523            "linux-hello-new": "bbl.bin",
524            "linux-hello-smp-new": "bbl.bin",
525            "povray": "_700480000000_.gz",
526            "mcf": "_17520000000_.gz",
527            "xalancbmk": "_266100000000_.gz",
528            "gcc": "_39720000000_.gz",
529            "namd": "_434640000000_.gz",
530            "milc": "_103620000000_.gz",
531            "lbm": "_140840000000_.gz",
532            "gromacs": "_275480000000_.gz",
533            "wrf": "_1916220000000_.gz",
534            "astar": "_122060000000_.gz"
535        }
536        if name in workloads:
537            return [os.path.join("/nfs/home/share/ci-workloads", name, workloads[name])]
538        # select a random SPEC checkpoint
539        assert(name == "random")
540        all_cpt = [
541            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gcb_o2_20m/take_cpt",
542            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gcb_o3_20m/take_cpt",
543            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gc_o2_20m/take_cpt",
544            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gc_o2_50m/take_cpt",
545            "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gcb_o2_20m/take_cpt",
546            "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gcb_o3_20m/take_cpt",
547            "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gc_o2_50m/take_cpt",
548            "/nfs-nvme/home/share/checkpoints_profiles/spec17_speed_rv64gcb_o3_20m/take_cpt"
549        ]
550        all_json = [
551            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gcb_o2_20m/json/simpoint_summary.json",
552            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gcb_o3_20m/simpoint_summary.json",
553            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gc_o2_20m/simpoint_summary.json",
554            "/nfs-nvme/home/share/checkpoints_profiles/spec06_rv64gc_o2_50m/simpoint_summary.json",
555            "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gcb_o2_20m/simpoint_summary.json",
556            "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gcb_o3_20m/simpoint_summary.json",
557            "/nfs-nvme/home/share/checkpoints_profiles/spec17_rv64gc_o2_50m/simpoint_summary.json",
558            "/nfs-nvme/home/share/checkpoints_profiles/spec17_speed_rv64gcb_o3_20m/simpoint_summary.json"
559        ]
560        assert(len(all_cpt) == len(all_json))
561        cpt_path, json_path = random.choice(list(zip(all_cpt, all_json)))
562        all_gcpt = load_all_gcpt(cpt_path, json_path)
563        return [random.choice(all_gcpt)]
564
565    def run_ci(self, test):
566        all_tests = {
567            "cputest": self.__get_ci_cputest,
568            "riscv-tests": self.__get_ci_rvtest,
569            "misc-tests": self.__get_ci_misc,
570            "mc-tests": self.__get_ci_mc,
571            "nodiff-tests": self.__get_ci_nodiff,
572            "rvh-tests": self.__get_ci_rvhtest,
573            "microbench": self.__am_apps_path,
574            "coremark": self.__am_apps_path,
575            "coremark-1-iteration": self.__am_apps_path,
576            "rvv-bench": self.__get_ci_rvvbench,
577            "rvv-test": self.__get_ci_rvvtest,
578            "f16_test": self.__get_ci_F16test,
579            "zcb-test": self.__get_ci_zcbtest
580        }
581        for target in all_tests.get(test, self.__get_ci_workloads)(test):
582            print(target)
583            ret = self.run_emu(target)
584            if ret:
585                if self.args.default_wave_home != self.args.wave_home:
586                    print("copy wave file to " + self.args.wave_home)
587                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.vcd $WAVE_HOME")
588                    self.__exec_cmd(f"cp $NOOP_HOME/build/emu $WAVE_HOME")
589                    self.__exec_cmd(f"cp $NOOP_HOME/build/rtl/SimTop.v $WAVE_HOME")
590                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.db $WAVE_HOME")
591                return ret
592        return 0
593
594    def run_ci_vcs(self, test):
595        all_tests = {
596            "cputest": self.__get_ci_cputest,
597            "riscv-tests": self.__get_ci_rvtest,
598            "misc-tests": self.__get_ci_misc,
599            "mc-tests": self.__get_ci_mc,
600            "nodiff-tests": self.__get_ci_nodiff,
601            "rvh-tests": self.__get_ci_rvhtest,
602            "microbench": self.__am_apps_path,
603            "coremark": self.__am_apps_path,
604            "coremark-1-iteration": self.__am_apps_path,
605            "rvv-bench": self.__get_ci_rvvbench,
606            "rvv-test": self.__get_ci_rvvtest,
607            "f16_test": self.__get_ci_F16test,
608            "zcb-test": self.__get_ci_zcbtest
609        }
610        for target in all_tests.get(test, self.__get_ci_workloads)(test):
611            print(target)
612            ret = self.run_simv(target)
613            if ret:
614                if self.args.default_wave_home != self.args.wave_home:
615                    print("copy wave file to " + self.args.wave_home)
616                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.fsdb $WAVE_HOME")
617                    self.__exec_cmd(f"cp $NOOP_HOME/build/simv $WAVE_HOME")
618                    self.__exec_cmd(f"cp $NOOP_HOME/build/rtl/SimTop.v $WAVE_HOME")
619                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.db $WAVE_HOME")
620                return ret
621        return 0
622
623def get_free_cores(n):
624    numa_re = re.compile(r'.*numactl +.*-C +([0-9]+)-([0-9]+).*')
625    while True:
626        disable_cores = []
627        for proc in psutil.process_iter():
628            try:
629                joint = ' '.join(proc.cmdline())
630                numa_match = numa_re.match(joint)
631                if numa_match and 'ssh' not in proc.name():
632                    disable_cores.extend(range(int(numa_match.group(1)), int(numa_match.group(2)) + 1))
633            except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
634                pass
635        num_logical_core = psutil.cpu_count(logical=False)
636        core_usage = psutil.cpu_percent(interval=1, percpu=True)
637        num_window = num_logical_core // n
638        for i in range(num_window):
639            if set(disable_cores) & set(range(i * n, i * n + n)):
640                continue
641            window_usage = core_usage[i * n : i * n + n]
642            if sum(window_usage) < 30 * n and True not in map(lambda x: x > 90, window_usage):
643                return (((i * n) % num_logical_core) // (num_logical_core // 2), i * n, i * n + n - 1)
644        print(f"No free {n} cores found. CPU usage: {core_usage}\n")
645        time.sleep(random.uniform(1, 60))
646
647if __name__ == "__main__":
648    parser = argparse.ArgumentParser(description='Python wrapper for XiangShan')
649    parser.add_argument('workload', nargs='?', type=str, default="",
650                        help='input workload file in binary format')
651    # actions
652    parser.add_argument('--build', action='store_true', help='build XS emu')
653    parser.add_argument('--generate', action='store_true', help='generate XS verilog')
654    parser.add_argument('--vcs-gen', action='store_true', help='generate XS sim verilog for vcs')
655    parser.add_argument('--vcs-build', action='store_true', help='build XS simv')
656    parser.add_argument('--ci', nargs='?', type=str, const="", help='run CI tests')
657    parser.add_argument('--ci-vcs', nargs='?', type=str, const="", help='run CI tests on simv')
658    parser.add_argument('--clean', action='store_true', help='clean up XiangShan CI workspace')
659    parser.add_argument('--timeout', nargs='?', type=int, default=None, help='timeout (in seconds)')
660    # environment variables
661    parser.add_argument('--nemu', nargs='?', type=str, help='path to nemu')
662    parser.add_argument('--am', nargs='?', type=str, help='path to nexus-am')
663    parser.add_argument('--dramsim3', nargs='?', type=str, help='path to dramsim3')
664    parser.add_argument('--rvtest', nargs='?', type=str, help='path to riscv-tests')
665    parser.add_argument('--wave-dump', nargs='?', type=str , help='path to dump wave')
666    # chisel arguments
667    parser.add_argument('--enable-log', action='store_true', help='enable log')
668    parser.add_argument('--num-cores', type=int, help='number of cores')
669    # makefile arguments
670    parser.add_argument('--release', action='store_true', help='enable release')
671    parser.add_argument('--spike', action='store_true', help='enable spike diff')
672    parser.add_argument('--with-dramsim3', action='store_true', help='enable dramsim3')
673    parser.add_argument('--threads', nargs='?', type=int, help='number of emu threads')
674    parser.add_argument('--trace', action='store_true', help='enable vcd waveform')
675    parser.add_argument('--trace-fst', action='store_true', help='enable fst waveform')
676    parser.add_argument('--config', nargs='?', type=str, help='config')
677    parser.add_argument('--emu-optimize', nargs='?', type=str, help='verilator optimization letter')
678    parser.add_argument('--xprop', action='store_true', help='enable xprop for vcs')
679    # emu arguments
680    parser.add_argument('--numa', action='store_true', help='use numactl')
681    parser.add_argument('--diff', nargs='?', default="./ready-to-run/riscv64-nemu-interpreter-so", type=str, help='nemu so')
682    parser.add_argument('--max-instr', nargs='?', type=int, help='max instr')
683    parser.add_argument('--disable-fork', action='store_true', help='disable lightSSS')
684    parser.add_argument('--no-diff', action='store_true', help='disable difftest')
685    parser.add_argument('--ram-size', nargs='?', type=str, help='manually set simulation memory size (8GB by default)')
686    parser.add_argument('--gcpt-restore-bin', type=str, default="", help="specify the bin used to restore from gcpt")
687    # both makefile and emu arguments
688    parser.add_argument('--no-db', action='store_true', help='disable chiseldb dump')
689    parser.add_argument('--pgo', nargs='?', type=str, help='workload for pgo (null to disable pgo)')
690    parser.add_argument('--pgo-max-cycle', nargs='?', default=400000, type=int, help='maximun cycle to train pgo')
691    parser.add_argument('--pgo-emu-args', nargs='?', default='--no-diff', type=str, help='emu arguments for pgo')
692    parser.add_argument('--llvm-profdata', nargs='?', type=str, help='corresponding llvm-profdata command of clang to compile emu, do not set with GCC')
693
694    args = parser.parse_args()
695
696    xs = XiangShan(args)
697    ret = xs.run(args)
698
699    sys.exit(ret)
700