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