xref: /XiangShan/scripts/xiangshan.py (revision 28bf033072e58fb2e3fb77753a0732d42e2de069)
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 find_files_with_suffix(root_dir, suffixes):
33    matching_files = []
34    for dirpath, _, filenames in os.walk(root_dir):
35        for filename in filenames:
36            if any(filename.endswith(suffix) for suffix in suffixes):
37                absolute_path = os.path.join(dirpath, filename)
38                matching_files.append(absolute_path)
39    return matching_files
40
41def load_all_gcpt(gcpt_paths):
42    all_gcpt = []
43    for gcpt_path in gcpt_paths:
44        all_gcpt.extend(find_files_with_suffix(gcpt_path, ['.zstd', '.gz']))
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        self.emu_optimize = args.emu_optimize
82        self.xprop = 1 if args.xprop else None
83        self.issue = args.issue
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            (self.issue,         "ISSUE"),
145        ]
146        args = filter(lambda arg: arg[0] is not None, makefile_args)
147        args = [(shlex.quote(str(arg[0])), arg[1]) for arg in args] # shell escape
148        return args
149
150    def get_emu_args(self):
151        emu_args = [
152            (self.max_instr, "max-instr"),
153            (self.diff,      "diff"),
154            (self.seed,      "seed"),
155            (self.ram_size,  "ram-size"),
156        ]
157        args = filter(lambda arg: arg[0] is not None, emu_args)
158        return args
159
160    def show(self):
161        print("Extra environment variables:")
162        env = self.get_env_variables()
163        for env_name in env:
164            print(f"{env_name}: {env[env_name]}")
165        print()
166        print("Chisel arguments:")
167        print(" ".join(self.get_chisel_args()))
168        print()
169        print("Makefile arguments:")
170        for val, name in self.get_makefile_args():
171            print(f"{name}={val}")
172        print()
173        print("emu arguments:")
174        for val, name in self.get_emu_args():
175            print(f"--{name} {val}")
176        print()
177
178    def __extract_path(self, path, env=None, default=None):
179        if path is None and env is not None:
180            path = os.getenv(env)
181        if path is None and default is not None:
182            path = default
183        path = os.path.realpath(path)
184        return path
185
186    def set_noop_home(self, path):
187        self.noop_home = path
188
189    def set_nemu_home(self, path):
190        self.nemu_home = path
191
192    def set_am_home(self, path):
193        self.am_home = path
194
195    def set_dramsim3_home(self, path):
196        self.dramsim3_home = path
197
198    def set_rvtest_home(self, path):
199        self.rvtest_home = path
200
201    def set_wave_home(self, path):
202        print(f"set wave home to {path}")
203        self.wave_home = path
204
205# XiangShan environment
206class XiangShan(object):
207    def __init__(self, args):
208        self.args = XSArgs(args)
209        self.timeout = args.timeout
210
211    def show(self):
212        self.args.show()
213
214    def make_clean(self):
215        print("Clean up CI workspace")
216        self.show()
217        return_code = self.__exec_cmd(f'make -C $NOOP_HOME clean')
218        return return_code
219
220    def generate_verilog(self):
221        print("Generating XiangShan verilog with the following configurations:")
222        self.show()
223        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
224        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
225        return_code = self.__exec_cmd(f'make -C $NOOP_HOME verilog SIM_ARGS="{sim_args}" {make_args}')
226        return return_code
227
228    def generate_sim_verilog(self):
229        print("Generating XiangShan sim-verilog with the following configurations:")
230        self.show()
231        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
232        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
233        return_code = self.__exec_cmd(f'make -C $NOOP_HOME sim-verilog SIM_ARGS="{sim_args}" {make_args}')
234        return return_code
235
236    def build_emu(self):
237        print("Building XiangShan emu with the following configurations:")
238        self.show()
239        sim_args = " ".join(self.args.get_chisel_args(prefix="--"))
240        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
241        return_code = self.__exec_cmd(f'make -C $NOOP_HOME emu -j200 SIM_ARGS="{sim_args}" {make_args}')
242        return return_code
243
244    def build_simv(self):
245        print("Building XiangShan simv with the following configurations")
246        self.show()
247        make_args = " ".join(map(lambda arg: f"{arg[1]}={arg[0]}", self.args.get_makefile_args()))
248        # TODO: make the following commands grouped as unseen scripts
249        return_code = self.__exec_cmd(f'\
250            eval `/usr/bin/modulecmd zsh load license`;\
251            eval `/usr/bin/modulecmd zsh load synopsys/vcs/Q-2020.03-SP2`;\
252            eval `/usr/bin/modulecmd zsh load synopsys/verdi/S-2021.09-SP1`;\
253            VERDI_HOME=/nfs/tools/synopsys/verdi/S-2021.09-SP1 \
254            make -C $NOOP_HOME simv {make_args} CONSIDER_FSDB=1')  # set CONSIDER_FSDB for compatibility
255        return return_code
256
257    def run_emu(self, workload):
258        print("Running XiangShan emu with the following configurations:")
259        self.show()
260        emu_args = " ".join(map(lambda arg: f"--{arg[1]} {arg[0]}", self.args.get_emu_args()))
261        print("workload:", workload)
262        numa_args = ""
263        if self.args.numa:
264            numa_info = get_free_cores(self.args.threads)
265            numa_args = f"numactl -m {numa_info[0]} -C {numa_info[1]}-{numa_info[2]}"
266        fork_args = "--enable-fork" if self.args.fork else ""
267        diff_args = "--no-diff" if self.args.disable_diff else ""
268        chiseldb_args = "--dump-db" if not self.args.disable_db else ""
269        gcpt_restore_args = f"-r {self.args.gcpt_restore_bin}" if len(self.args.gcpt_restore_bin) != 0 else ""
270        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}')
271        return return_code
272
273    def run_simv(self, workload):
274        print("Running XiangShan simv with the following configurations:")
275        self.show()
276        diff_args = "$NOOP_HOME/"+ args.diff
277        assert_args = "-assert finish_maxfail=30 -assert global_finish_maxfail=10000"
278        return_code = self.__exec_cmd(f'cd $NOOP_HOME/build && ./simv +workload={workload} +diff={diff_args} +dump-wave=fsdb {assert_args} | tee simv.log')
279        with open(f"{self.args.noop_home}/build/simv.log") as f:
280            content = f.read()
281            if "Offending" in content or "HIT GOOD TRAP" not in content:
282                return 1
283        return return_code
284
285    def run(self, args):
286        if args.ci is not None:
287            return self.run_ci(args.ci)
288        if args.ci_vcs is not None:
289            return self.run_ci_vcs(args.ci_vcs)
290        actions = [
291            (args.generate, lambda _ : self.generate_verilog()),
292            (args.vcs_gen, lambda _ : self.generate_sim_verilog()),
293            (args.build, lambda _ : self.build_emu()),
294            (args.vcs_build, lambda _ : self.build_simv()),
295            (args.workload, lambda args: self.run_emu(args.workload)),
296            (args.clean, lambda _ : self.make_clean())
297        ]
298        valid_actions = map(lambda act: act[1], filter(lambda act: act[0], actions))
299        for i, action in enumerate(valid_actions):
300            print(f"Action {i}:")
301            ret = action(args)
302            if ret:
303                return ret
304        return 0
305
306    def __exec_cmd(self, cmd):
307        env = dict(os.environ)
308        env.update(self.args.get_env_variables())
309        print("subprocess call cmd:", cmd)
310        start = time.time()
311        proc = subprocess.Popen(cmd, shell=True, env=env, preexec_fn=os.setsid)
312        try:
313            return_code = proc.wait(self.timeout)
314            end = time.time()
315            print(f"Elapsed time: {end - start} seconds")
316            return return_code
317        except (KeyboardInterrupt, subprocess.TimeoutExpired):
318            os.killpg(os.getpgid(proc.pid), signal.SIGINT)
319            print(f"KeyboardInterrupt or TimeoutExpired.")
320            return 0
321
322    def __get_ci_cputest(self, name=None):
323        # base_dir = os.path.join(self.args.am_home, "tests/cputest/build")
324        base_dir = "/nfs/home/share/ci-workloads/nexus-am-workloads/tests/cputest"
325        cputest = os.listdir(base_dir)
326        cputest = filter(lambda x: x.endswith(".bin"), cputest)
327        cputest = map(lambda x: os.path.join(base_dir, x), cputest)
328        return cputest
329
330    def __get_ci_rvtest(self, name=None):
331        base_dir = os.path.join(self.args.rvtest_home, "isa/build")
332        riscv_tests = os.listdir(base_dir)
333        riscv_tests = filter(lambda x: x.endswith(".bin"), riscv_tests)
334        all_rv_tests = ["rv64ui", "rv64um", "rv64ua", "rv64uf", "rv64ud", "rv64mi"]
335        riscv_tests = filter(lambda x: x[:6] in all_rv_tests, riscv_tests)
336        riscv_tests = map(lambda x: os.path.join(base_dir, x), riscv_tests)
337        return riscv_tests
338
339    def __get_ci_misc(self, name=None):
340        base_dir = "/nfs/home/share/ci-workloads"
341        workloads = [
342            "bitmanip/bitMisc.bin",
343            "crypto/crypto-riscv64-noop.bin",
344            # "coremark_rv64gc_o2/coremark-riscv64-xs.bin",
345            # "coremark_rv64gc_o3/coremark-riscv64-xs.bin",
346            # "coremark_rv64gcb_o3/coremark-riscv64-xs.bin",
347            "nexus-am-workloads/amtest/external_intr-riscv64-xs.bin",
348            "nexus-am-workloads/tests/aliastest/aliastest-riscv64-xs.bin",
349            "Svinval/rv64mi-p-svinval.bin",
350            "pmp/pmp.riscv.bin",
351            "nexus-am-workloads/amtest/pmp_test-riscv64-xs.bin",
352            "nexus-am-workloads/amtest/sv39_hp_atom_test-riscv64-xs.bin",
353            "asid/asid.bin",
354            "isa_misc/xret_clear_mprv.bin",
355            "isa_misc/satp_ppn.bin",
356            "cache-management/softprefetchtest-riscv64-xs.bin",
357            "smstateen/rvh_test.bin",
358            "zacas/zacas-riscv64-xs.bin",
359            "Svpbmt/rvh_test.bin",
360            "Svnapot/svnapot-test.bin"
361        ]
362        misc_tests = map(lambda x: os.path.join(base_dir, x), workloads)
363        return misc_tests
364
365    def __get_ci_rvhtest(self, name=None):
366        base_dir = "/nfs/home/share/ci-workloads/H-extension-tests"
367        workloads = [
368            "riscv-hyp-tests/rvh_test.bin",
369            "xvisor_wboxtest/checkpoint.gz",
370            "pointer-masking-test/M_HS_test/rvh_test.bin",
371            "pointer-masking-test/U_test/hint_UMode_hupmm2/rvh_test.bin",
372            "pointer-masking-test/U_test/vu_senvcfgpmm2/rvh_test.bin"
373        ]
374        rvh_tests = map(lambda x: os.path.join(base_dir, x), workloads)
375        return rvh_tests
376
377    def __get_ci_rvvbench(self, name=None):
378        base_dir = "/nfs/home/share/ci-workloads"
379        workloads = [
380            "rvv-bench/poly1305.bin",
381            "rvv-bench/mergelines.bin"
382        ]
383        rvvbench = map(lambda x: os.path.join(base_dir, x), workloads)
384        return rvvbench
385
386    def __get_ci_rvvtest(self, name=None):
387        base_dir = "/nfs/home/share/ci-workloads/V-extension-tests"
388        workloads = [
389            "rvv-test/vluxei32.v-0.bin",
390            "rvv-test/vlsseg4e32.v-0.bin",
391            "rvv-test/vlseg4e32.v-0.bin",
392            "rvv-test/vsetvl-0.bin",
393            "rvv-test/vsetivli-0.bin",
394            "rvv-test/vsuxei32.v-0.bin",
395            "rvv-test/vse16.v-0.bin",
396            "rvv-test/vsse16.v-1.bin",
397            "rvv-test/vlse32.v-0.bin",
398            "rvv-test/vsetvli-0.bin",
399            "rvv-test/vle16.v-0.bin",
400            "rvv-test/vle32.v-0.bin",
401            "rvv-test/vfsgnj.vv-0.bin",
402            "rvv-test/vfadd.vf-0.bin",
403            "rvv-test/vfsub.vf-0.bin",
404            "rvv-test/vslide1down.vx-0.bin"
405        ]
406        rvv_test = map(lambda x: os.path.join(base_dir, x), workloads)
407        return rvv_test
408
409    def __get_ci_F16test(self, name=None):
410        base_dir = "/nfs/home/share/ci-workloads/vector/F16-tests/build"
411        workloads = [
412            "rv64uzfhmin-p-fzfhmincvt.bin",
413            "rv64uzfh-p-fadd.bin",
414            "rv64uzfh-p-fclass.bin",
415            "rv64uzfh-p-fcmp.bin",
416            "rv64uzfh-p-fcvt.bin",
417            "rv64uzfh-p-fcvt_w.bin",
418            "rv64uzfh-p-fdiv.bin",
419            "rv64uzfh-p-fmadd.bin",
420            "rv64uzfh-p-fmin.bin",
421            "rv64uzfh-p-ldst.bin",
422            "rv64uzfh-p-move.bin",
423            "rv64uzfh-p-recoding.bin",
424            "rv64uzvfh-p-vfadd.bin",
425            "rv64uzvfh-p-vfclass.bin",
426            "rv64uzvfh-p-vfcvtfx.bin",
427            "rv64uzvfh-p-vfcvtfxu.bin",
428            "rv64uzvfh-p-vfcvtrxf.bin",
429            "rv64uzvfh-p-vfcvtrxuf.bin",
430            "rv64uzvfh-p-vfcvtxf.bin",
431            "rv64uzvfh-p-vfcvtxuf.bin",
432            "rv64uzvfh-p-vfdiv.bin",
433            "rv64uzvfh-p-vfdown.bin",
434            "rv64uzvfh-p-vfmacc.bin",
435            "rv64uzvfh-p-vfmadd.bin",
436            "rv64uzvfh-p-vfmax.bin",
437            "rv64uzvfh-p-vfmerge.bin",
438            "rv64uzvfh-p-vfmin.bin",
439            "rv64uzvfh-p-vfmsac.bin",
440            "rv64uzvfh-p-vfmsub.bin",
441            "rv64uzvfh-p-vfmul.bin",
442            "rv64uzvfh-p-vfmv.bin",
443            "rv64uzvfh-p-vfncvtff.bin",
444            "rv64uzvfh-p-vfncvtfx.bin",
445            "rv64uzvfh-p-vfncvtfxu.bin",
446            "rv64uzvfh-p-vfncvtrff.bin",
447            "rv64uzvfh-p-vfncvtrxf.bin",
448            "rv64uzvfh-p-vfncvtrxuf.bin",
449            "rv64uzvfh-p-vfncvtxf.bin",
450            "rv64uzvfh-p-vfncvtxuf.bin",
451            "rv64uzvfh-p-vfnmacc.bin",
452            "rv64uzvfh-p-vfnmadd.bin",
453            "rv64uzvfh-p-vfnmsac.bin",
454            "rv64uzvfh-p-vfnmsub.bin",
455            "rv64uzvfh-p-vfrdiv.bin",
456            "rv64uzvfh-p-vfrec7.bin",
457            "rv64uzvfh-p-vfredmax.bin",
458            "rv64uzvfh-p-vfredmin.bin",
459            "rv64uzvfh-p-vfredosum.bin",
460            "rv64uzvfh-p-vfredusum.bin",
461            "rv64uzvfh-p-vfrsqrt7.bin",
462            "rv64uzvfh-p-vfrsub.bin",
463            "rv64uzvfh-p-vfsgnj.bin",
464            "rv64uzvfh-p-vfsgnjn.bin",
465            "rv64uzvfh-p-vfsgnjx.bin",
466            "rv64uzvfh-p-vfsqrt.bin",
467            "rv64uzvfh-p-vfsub.bin",
468            "rv64uzvfh-p-vfup.bin",
469            "rv64uzvfh-p-vfwadd.bin",
470            "rv64uzvfh-p-vfwadd-w.bin",
471            "rv64uzvfh-p-vfwcvtff.bin",
472            "rv64uzvfh-p-vfwcvtfx.bin",
473            "rv64uzvfh-p-vfwcvtfxu.bin",
474            "rv64uzvfh-p-vfwcvtrxf.bin",
475            "rv64uzvfh-p-vfwcvtrxuf.bin",
476            "rv64uzvfh-p-vfwcvtxf.bin",
477            "rv64uzvfh-p-vfwcvtxuf.bin",
478            "rv64uzvfh-p-vfwmacc.bin",
479            "rv64uzvfh-p-vfwmsac.bin",
480            "rv64uzvfh-p-vfwmul.bin",
481            "rv64uzvfh-p-vfwnmacc.bin",
482            "rv64uzvfh-p-vfwnmsac.bin",
483            "rv64uzvfh-p-vfwredosum.bin",
484            "rv64uzvfh-p-vfwredusum.bin",
485            "rv64uzvfh-p-vfwsub.bin",
486            "rv64uzvfh-p-vfwsub-w.bin",
487            "rv64uzvfh-p-vmfeq.bin",
488            "rv64uzvfh-p-vmfge.bin",
489            "rv64uzvfh-p-vmfgt.bin",
490            "rv64uzvfh-p-vmfle.bin",
491            "rv64uzvfh-p-vmflt.bin",
492            "rv64uzvfh-p-vmfne.bin"
493        ]
494        f16_test = map(lambda x: os.path.join(base_dir, x), workloads)
495        return f16_test
496    def __get_ci_zcbtest(self, name=None):
497        base_dir = "/nfs/home/share/ci-workloads/zcb-test"
498        workloads = [
499            "zcb-test-riscv64-xs.bin"
500        ]
501        zcb_test = map(lambda x: os.path.join(base_dir, x), workloads)
502        return zcb_test
503
504    def __get_ci_mc(self, name=None):
505        base_dir = "/nfs/home/share/ci-workloads"
506        workloads = [
507            "nexus-am-workloads/tests/dualcoretest/ldvio-riscv64-xs.bin"
508        ]
509        mc_tests = map(lambda x: os.path.join(base_dir, x), workloads)
510        return mc_tests
511
512    def __get_ci_nodiff(self, name=None):
513        base_dir = "/nfs/home/share/ci-workloads"
514        workloads = [
515            "cache-management/cacheoptest-riscv64-xs.bin"
516        ]
517        tests = map(lambda x: os.path.join(base_dir, x), workloads)
518        return tests
519
520    def __am_apps_path(self, bench):
521        base_dir = '/nfs/home/share/ci-workloads/nexus-am-workloads/apps'
522        filename = f"{bench}-riscv64-xs.bin"
523        return [os.path.join(base_dir, bench, filename)]
524
525    def __get_ci_workloads(self, name):
526        workloads = {
527            "linux-hello": "bbl.bin",
528            "linux-hello-smp": "bbl.bin",
529            "linux-hello-opensbi": "fw_payload.bin",
530            "linux-hello-smp-opensbi": "fw_payload.bin",
531            "linux-hello-new": "bbl.bin",
532            "linux-hello-smp-new": "bbl.bin",
533            "povray": "_700480000000_.gz",
534            "mcf": "_17520000000_.gz",
535            "xalancbmk": "_266100000000_.gz",
536            "gcc": "_39720000000_.gz",
537            "namd": "_434640000000_.gz",
538            "milc": "_103620000000_.gz",
539            "lbm": "_140840000000_.gz",
540            "gromacs": "_275480000000_.gz",
541            "wrf": "_1916220000000_.gz",
542            "astar": "_122060000000_.gz"
543        }
544        if name in workloads:
545            return [os.path.join("/nfs/home/share/ci-workloads", name, workloads[name])]
546        # select a random SPEC checkpoint
547        assert(name == "random")
548        all_cpt_dir = [
549            "/nfs/home/share/checkpoints_profiles/spec06_rv64gcb_o2_20m/take_cpt",
550            "/nfs/home/share/checkpoints_profiles/spec06_rv64gcb_o3_20m/take_cpt",
551            "/nfs/home/share/checkpoints_profiles/spec06_rv64gc_o2_20m/take_cpt",
552            "/nfs/home/share/checkpoints_profiles/spec06_rv64gc_o2_50m/take_cpt",
553            "/nfs/home/share/checkpoints_profiles/spec17_rv64gcb_o2_20m/take_cpt",
554            "/nfs/home/share/checkpoints_profiles/spec17_rv64gcb_o3_20m/take_cpt",
555            "/nfs/home/share/checkpoints_profiles/spec17_rv64gc_o2_50m/take_cpt",
556            "/nfs/home/share/checkpoints_profiles/spec17_speed_rv64gcb_o3_20m/take_cpt",
557            "/nfs/home/share/checkpoints_profiles/spec06_rv64gcb_O3_20m_gcc12.2.0-intFpcOff-jeMalloc/zstd-checkpoint-0-0-0",
558            "/nfs/home/share/checkpoints_profiles/spec06_gcc15_rv64gcbv_O3_lto_base_nemu_single_core_NEMU_archgroup_2024-10-12-16-05/checkpoint-0-0-0"
559        ]
560        all_gcpt = load_all_gcpt(all_cpt_dir)
561        return [random.choice(all_gcpt)]
562
563    def run_ci(self, test):
564        all_tests = {
565            "cputest": self.__get_ci_cputest,
566            "riscv-tests": self.__get_ci_rvtest,
567            "misc-tests": self.__get_ci_misc,
568            "mc-tests": self.__get_ci_mc,
569            "nodiff-tests": self.__get_ci_nodiff,
570            "rvh-tests": self.__get_ci_rvhtest,
571            "microbench": self.__am_apps_path,
572            "coremark": self.__am_apps_path,
573            "coremark-1-iteration": self.__am_apps_path,
574            "rvv-bench": self.__get_ci_rvvbench,
575            "rvv-test": self.__get_ci_rvvtest,
576            "f16_test": self.__get_ci_F16test,
577            "zcb-test": self.__get_ci_zcbtest
578        }
579        for target in all_tests.get(test, self.__get_ci_workloads)(test):
580            print(target)
581            ret = self.run_emu(target)
582            if ret:
583                if self.args.default_wave_home != self.args.wave_home:
584                    print("copy wave file to " + self.args.wave_home)
585                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.vcd $WAVE_HOME")
586                    self.__exec_cmd(f"cp $NOOP_HOME/build/emu $WAVE_HOME")
587                    self.__exec_cmd(f"cp $NOOP_HOME/build/rtl/SimTop.v $WAVE_HOME")
588                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.db $WAVE_HOME")
589                return ret
590        return 0
591
592    def run_ci_vcs(self, test):
593        all_tests = {
594            "cputest": self.__get_ci_cputest,
595            "riscv-tests": self.__get_ci_rvtest,
596            "misc-tests": self.__get_ci_misc,
597            "mc-tests": self.__get_ci_mc,
598            "nodiff-tests": self.__get_ci_nodiff,
599            "rvh-tests": self.__get_ci_rvhtest,
600            "microbench": self.__am_apps_path,
601            "coremark": self.__am_apps_path,
602            "coremark-1-iteration": self.__am_apps_path,
603            "rvv-bench": self.__get_ci_rvvbench,
604            "rvv-test": self.__get_ci_rvvtest,
605            "f16_test": self.__get_ci_F16test,
606            "zcb-test": self.__get_ci_zcbtest
607        }
608        for target in all_tests.get(test, self.__get_ci_workloads)(test):
609            print(target)
610            ret = self.run_simv(target)
611            if ret:
612                if self.args.default_wave_home != self.args.wave_home:
613                    print("copy wave file to " + self.args.wave_home)
614                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.fsdb $WAVE_HOME")
615                    self.__exec_cmd(f"cp $NOOP_HOME/build/simv $WAVE_HOME")
616                    self.__exec_cmd(f"cp $NOOP_HOME/build/rtl/SimTop.v $WAVE_HOME")
617                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.db $WAVE_HOME")
618                return ret
619        return 0
620
621def get_free_cores(n):
622    numa_re = re.compile(r'.*numactl +.*-C +([0-9]+)-([0-9]+).*')
623    while True:
624        disable_cores = []
625        for proc in psutil.process_iter():
626            try:
627                joint = ' '.join(proc.cmdline())
628                numa_match = numa_re.match(joint)
629                if numa_match and 'ssh' not in proc.name():
630                    disable_cores.extend(range(int(numa_match.group(1)), int(numa_match.group(2)) + 1))
631            except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
632                pass
633        num_logical_core = psutil.cpu_count(logical=False)
634        core_usage = psutil.cpu_percent(interval=1, percpu=True)
635        num_window = num_logical_core // n
636        for i in range(num_window):
637            if set(disable_cores) & set(range(i * n, i * n + n)):
638                continue
639            window_usage = core_usage[i * n : i * n + n]
640            if sum(window_usage) < 30 * n and True not in map(lambda x: x > 90, window_usage):
641                return (((i * n) % num_logical_core) // (num_logical_core // 2), i * n, i * n + n - 1)
642        print(f"No free {n} cores found. CPU usage: {core_usage}\n")
643        time.sleep(random.uniform(1, 60))
644
645if __name__ == "__main__":
646    parser = argparse.ArgumentParser(description='Python wrapper for XiangShan')
647    parser.add_argument('workload', nargs='?', type=str, default="",
648                        help='input workload file in binary format')
649    # actions
650    parser.add_argument('--build', action='store_true', help='build XS emu')
651    parser.add_argument('--generate', action='store_true', help='generate XS verilog')
652    parser.add_argument('--vcs-gen', action='store_true', help='generate XS sim verilog for vcs')
653    parser.add_argument('--vcs-build', action='store_true', help='build XS simv')
654    parser.add_argument('--ci', nargs='?', type=str, const="", help='run CI tests')
655    parser.add_argument('--ci-vcs', nargs='?', type=str, const="", help='run CI tests on simv')
656    parser.add_argument('--clean', action='store_true', help='clean up XiangShan CI workspace')
657    parser.add_argument('--timeout', nargs='?', type=int, default=None, help='timeout (in seconds)')
658    # environment variables
659    parser.add_argument('--nemu', nargs='?', type=str, help='path to nemu')
660    parser.add_argument('--am', nargs='?', type=str, help='path to nexus-am')
661    parser.add_argument('--dramsim3', nargs='?', type=str, help='path to dramsim3')
662    parser.add_argument('--rvtest', nargs='?', type=str, help='path to riscv-tests')
663    parser.add_argument('--wave-dump', nargs='?', type=str , help='path to dump wave')
664    # chisel arguments
665    parser.add_argument('--enable-log', action='store_true', help='enable log')
666    parser.add_argument('--num-cores', type=int, help='number of cores')
667    # makefile arguments
668    parser.add_argument('--release', action='store_true', help='enable release')
669    parser.add_argument('--spike', action='store_true', help='enable spike diff')
670    parser.add_argument('--with-dramsim3', action='store_true', help='enable dramsim3')
671    parser.add_argument('--threads', nargs='?', type=int, help='number of emu threads')
672    parser.add_argument('--trace', action='store_true', help='enable vcd waveform')
673    parser.add_argument('--trace-fst', action='store_true', help='enable fst waveform')
674    parser.add_argument('--config', nargs='?', type=str, help='config')
675    parser.add_argument('--emu-optimize', nargs='?', type=str, help='verilator optimization letter')
676    parser.add_argument('--xprop', action='store_true', help='enable xprop for vcs')
677    parser.add_argument('--issue', nargs='?', type=str, help='CHI issue')
678    # emu arguments
679    parser.add_argument('--numa', action='store_true', help='use numactl')
680    parser.add_argument('--diff', nargs='?', default="./ready-to-run/riscv64-nemu-interpreter-so", type=str, help='nemu so')
681    parser.add_argument('--max-instr', nargs='?', type=int, help='max instr')
682    parser.add_argument('--disable-fork', action='store_true', help='disable lightSSS')
683    parser.add_argument('--no-diff', action='store_true', help='disable difftest')
684    parser.add_argument('--ram-size', nargs='?', type=str, help='manually set simulation memory size (8GB by default)')
685    parser.add_argument('--gcpt-restore-bin', type=str, default="", help="specify the bin used to restore from gcpt")
686    # both makefile and emu arguments
687    parser.add_argument('--no-db', action='store_true', help='disable chiseldb dump')
688    parser.add_argument('--pgo', nargs='?', type=str, help='workload for pgo (null to disable pgo)')
689    parser.add_argument('--pgo-max-cycle', nargs='?', default=400000, type=int, help='maximun cycle to train pgo')
690    parser.add_argument('--pgo-emu-args', nargs='?', default='--no-diff', type=str, help='emu arguments for pgo')
691    parser.add_argument('--llvm-profdata', nargs='?', type=str, help='corresponding llvm-profdata command of clang to compile emu, do not set with GCC')
692
693    args = parser.parse_args()
694
695    xs = XiangShan(args)
696    ret = xs.run(args)
697
698    sys.exit(ret)
699