xref: /XiangShan/scripts/xiangshan.py (revision 519244c70fb0960c61014ad524e609af0918a818)
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            "zacas/zacas-riscv64-xs.bin",
357            "Svpbmt/rvh_test.bin"
358        ]
359        misc_tests = map(lambda x: os.path.join(base_dir, x), workloads)
360        return misc_tests
361
362    def __get_ci_rvhtest(self, name=None):
363        base_dir = "/nfs/home/share/ci-workloads/H-extension-tests"
364        workloads = [
365            "riscv-hyp-tests/rvh_test.bin",
366            "xvisor_wboxtest/checkpoint.gz",
367            "pointer-masking-test/M_HS_test/rvh_test.bin",
368            "pointer-masking-test/U_test/hint_UMode_hupmm2/rvh_test.bin",
369            "pointer-masking-test/U_test/vu_senvcfgpmm2/rvh_test.bin"
370        ]
371        rvh_tests = map(lambda x: os.path.join(base_dir, x), workloads)
372        return rvh_tests
373
374    def __get_ci_rvvbench(self, name=None):
375        base_dir = "/nfs/home/share/ci-workloads"
376        workloads = [
377            "rvv-bench/poly1305.bin",
378            "rvv-bench/mergelines.bin"
379        ]
380        rvvbench = map(lambda x: os.path.join(base_dir, x), workloads)
381        return rvvbench
382
383    def __get_ci_rvvtest(self, name=None):
384        base_dir = "/nfs/home/share/ci-workloads/V-extension-tests"
385        workloads = [
386            "rvv-test/vluxei32.v-0.bin",
387            "rvv-test/vlsseg4e32.v-0.bin",
388            "rvv-test/vlseg4e32.v-0.bin",
389            "rvv-test/vsetvl-0.bin",
390            "rvv-test/vsetivli-0.bin",
391            "rvv-test/vsuxei32.v-0.bin",
392            "rvv-test/vse16.v-0.bin",
393            "rvv-test/vsse16.v-1.bin",
394            "rvv-test/vlse32.v-0.bin",
395            "rvv-test/vsetvli-0.bin",
396            "rvv-test/vle16.v-0.bin",
397            "rvv-test/vle32.v-0.bin",
398            "rvv-test/vfsgnj.vv-0.bin",
399            "rvv-test/vfadd.vf-0.bin",
400            "rvv-test/vfsub.vf-0.bin",
401            "rvv-test/vslide1down.vx-0.bin"
402        ]
403        rvv_test = map(lambda x: os.path.join(base_dir, x), workloads)
404        return rvv_test
405
406    def __get_ci_F16test(self, name=None):
407        base_dir = "/nfs/home/share/ci-workloads/vector/F16-tests/build"
408        workloads = [
409            "rv64uzfhmin-p-fzfhmincvt.bin",
410            "rv64uzfh-p-fadd.bin",
411            "rv64uzfh-p-fclass.bin",
412            "rv64uzfh-p-fcmp.bin",
413            "rv64uzfh-p-fcvt.bin",
414            "rv64uzfh-p-fcvt_w.bin",
415            "rv64uzfh-p-fdiv.bin",
416            "rv64uzfh-p-fmadd.bin",
417            "rv64uzfh-p-fmin.bin",
418            "rv64uzfh-p-ldst.bin",
419            "rv64uzfh-p-move.bin",
420            "rv64uzfh-p-recoding.bin",
421            "rv64uzvfh-p-vfadd.bin",
422            "rv64uzvfh-p-vfclass.bin",
423            "rv64uzvfh-p-vfcvtfx.bin",
424            "rv64uzvfh-p-vfcvtfxu.bin",
425            "rv64uzvfh-p-vfcvtrxf.bin",
426            "rv64uzvfh-p-vfcvtrxuf.bin",
427            "rv64uzvfh-p-vfcvtxf.bin",
428            "rv64uzvfh-p-vfcvtxuf.bin",
429            "rv64uzvfh-p-vfdiv.bin",
430            "rv64uzvfh-p-vfdown.bin",
431            "rv64uzvfh-p-vfmacc.bin",
432            "rv64uzvfh-p-vfmadd.bin",
433            "rv64uzvfh-p-vfmax.bin",
434            "rv64uzvfh-p-vfmerge.bin",
435            "rv64uzvfh-p-vfmin.bin",
436            "rv64uzvfh-p-vfmsac.bin",
437            "rv64uzvfh-p-vfmsub.bin",
438            "rv64uzvfh-p-vfmul.bin",
439            "rv64uzvfh-p-vfmv.bin",
440            "rv64uzvfh-p-vfncvtff.bin",
441            "rv64uzvfh-p-vfncvtfx.bin",
442            "rv64uzvfh-p-vfncvtfxu.bin",
443            "rv64uzvfh-p-vfncvtrff.bin",
444            "rv64uzvfh-p-vfncvtrxf.bin",
445            "rv64uzvfh-p-vfncvtrxuf.bin",
446            "rv64uzvfh-p-vfncvtxf.bin",
447            "rv64uzvfh-p-vfncvtxuf.bin",
448            "rv64uzvfh-p-vfnmacc.bin",
449            "rv64uzvfh-p-vfnmadd.bin",
450            "rv64uzvfh-p-vfnmsac.bin",
451            "rv64uzvfh-p-vfnmsub.bin",
452            "rv64uzvfh-p-vfrdiv.bin",
453            "rv64uzvfh-p-vfrec7.bin",
454            "rv64uzvfh-p-vfredmax.bin",
455            "rv64uzvfh-p-vfredmin.bin",
456            "rv64uzvfh-p-vfredosum.bin",
457            "rv64uzvfh-p-vfredusum.bin",
458            "rv64uzvfh-p-vfrsqrt7.bin",
459            "rv64uzvfh-p-vfrsub.bin",
460            "rv64uzvfh-p-vfsgnj.bin",
461            "rv64uzvfh-p-vfsgnjn.bin",
462            "rv64uzvfh-p-vfsgnjx.bin",
463            "rv64uzvfh-p-vfsqrt.bin",
464            "rv64uzvfh-p-vfsub.bin",
465            "rv64uzvfh-p-vfup.bin",
466            "rv64uzvfh-p-vfwadd.bin",
467            "rv64uzvfh-p-vfwadd-w.bin",
468            "rv64uzvfh-p-vfwcvtff.bin",
469            "rv64uzvfh-p-vfwcvtfx.bin",
470            "rv64uzvfh-p-vfwcvtfxu.bin",
471            "rv64uzvfh-p-vfwcvtrxf.bin",
472            "rv64uzvfh-p-vfwcvtrxuf.bin",
473            "rv64uzvfh-p-vfwcvtxf.bin",
474            "rv64uzvfh-p-vfwcvtxuf.bin",
475            "rv64uzvfh-p-vfwmacc.bin",
476            "rv64uzvfh-p-vfwmsac.bin",
477            "rv64uzvfh-p-vfwmul.bin",
478            "rv64uzvfh-p-vfwnmacc.bin",
479            "rv64uzvfh-p-vfwnmsac.bin",
480            "rv64uzvfh-p-vfwredosum.bin",
481            "rv64uzvfh-p-vfwredusum.bin",
482            "rv64uzvfh-p-vfwsub.bin",
483            "rv64uzvfh-p-vfwsub-w.bin",
484            "rv64uzvfh-p-vmfeq.bin",
485            "rv64uzvfh-p-vmfge.bin",
486            "rv64uzvfh-p-vmfgt.bin",
487            "rv64uzvfh-p-vmfle.bin",
488            "rv64uzvfh-p-vmflt.bin",
489            "rv64uzvfh-p-vmfne.bin"
490        ]
491        f16_test = map(lambda x: os.path.join(base_dir, x), workloads)
492        return f16_test
493    def __get_ci_zcbtest(self, name=None):
494        base_dir = "/nfs/home/share/ci-workloads/zcb-test"
495        workloads = [
496            "zcb-test-riscv64-xs.bin"
497        ]
498        zcb_test = map(lambda x: os.path.join(base_dir, x), workloads)
499        return zcb_test
500
501    def __get_ci_mc(self, name=None):
502        base_dir = "/nfs/home/share/ci-workloads"
503        workloads = [
504            "nexus-am-workloads/tests/dualcoretest/ldvio-riscv64-xs.bin"
505        ]
506        mc_tests = map(lambda x: os.path.join(base_dir, x), workloads)
507        return mc_tests
508
509    def __get_ci_nodiff(self, name=None):
510        base_dir = "/nfs/home/share/ci-workloads"
511        workloads = [
512            "cache-management/cacheoptest-riscv64-xs.bin"
513        ]
514        tests = map(lambda x: os.path.join(base_dir, x), workloads)
515        return tests
516
517    def __am_apps_path(self, bench):
518        base_dir = '/nfs/home/share/ci-workloads/nexus-am-workloads/apps'
519        filename = f"{bench}-riscv64-xs.bin"
520        return [os.path.join(base_dir, bench, filename)]
521
522    def __get_ci_workloads(self, name):
523        workloads = {
524            "linux-hello": "bbl.bin",
525            "linux-hello-smp": "bbl.bin",
526            "linux-hello-opensbi": "fw_payload.bin",
527            "linux-hello-smp-opensbi": "fw_payload.bin",
528            "linux-hello-new": "bbl.bin",
529            "linux-hello-smp-new": "bbl.bin",
530            "povray": "_700480000000_.gz",
531            "mcf": "_17520000000_.gz",
532            "xalancbmk": "_266100000000_.gz",
533            "gcc": "_39720000000_.gz",
534            "namd": "_434640000000_.gz",
535            "milc": "_103620000000_.gz",
536            "lbm": "_140840000000_.gz",
537            "gromacs": "_275480000000_.gz",
538            "wrf": "_1916220000000_.gz",
539            "astar": "_122060000000_.gz"
540        }
541        if name in workloads:
542            return [os.path.join("/nfs/home/share/ci-workloads", name, workloads[name])]
543        # select a random SPEC checkpoint
544        assert(name == "random")
545        all_cpt_dir = [
546            "/nfs/home/share/checkpoints_profiles/spec06_rv64gcb_o2_20m/take_cpt",
547            "/nfs/home/share/checkpoints_profiles/spec06_rv64gcb_o3_20m/take_cpt",
548            "/nfs/home/share/checkpoints_profiles/spec06_rv64gc_o2_20m/take_cpt",
549            "/nfs/home/share/checkpoints_profiles/spec06_rv64gc_o2_50m/take_cpt",
550            "/nfs/home/share/checkpoints_profiles/spec17_rv64gcb_o2_20m/take_cpt",
551            "/nfs/home/share/checkpoints_profiles/spec17_rv64gcb_o3_20m/take_cpt",
552            "/nfs/home/share/checkpoints_profiles/spec17_rv64gc_o2_50m/take_cpt",
553            "/nfs/home/share/checkpoints_profiles/spec17_speed_rv64gcb_o3_20m/take_cpt",
554            "/nfs/home/share/checkpoints_profiles/spec06_rv64gcb_O3_20m_gcc12.2.0-intFpcOff-jeMalloc/zstd-checkpoint-0-0-0",
555            "/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"
556        ]
557        all_gcpt = load_all_gcpt(all_cpt_dir)
558        return [random.choice(all_gcpt)]
559
560    def run_ci(self, test):
561        all_tests = {
562            "cputest": self.__get_ci_cputest,
563            "riscv-tests": self.__get_ci_rvtest,
564            "misc-tests": self.__get_ci_misc,
565            "mc-tests": self.__get_ci_mc,
566            "nodiff-tests": self.__get_ci_nodiff,
567            "rvh-tests": self.__get_ci_rvhtest,
568            "microbench": self.__am_apps_path,
569            "coremark": self.__am_apps_path,
570            "coremark-1-iteration": self.__am_apps_path,
571            "rvv-bench": self.__get_ci_rvvbench,
572            "rvv-test": self.__get_ci_rvvtest,
573            "f16_test": self.__get_ci_F16test,
574            "zcb-test": self.__get_ci_zcbtest
575        }
576        for target in all_tests.get(test, self.__get_ci_workloads)(test):
577            print(target)
578            ret = self.run_emu(target)
579            if ret:
580                if self.args.default_wave_home != self.args.wave_home:
581                    print("copy wave file to " + self.args.wave_home)
582                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.vcd $WAVE_HOME")
583                    self.__exec_cmd(f"cp $NOOP_HOME/build/emu $WAVE_HOME")
584                    self.__exec_cmd(f"cp $NOOP_HOME/build/rtl/SimTop.v $WAVE_HOME")
585                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.db $WAVE_HOME")
586                return ret
587        return 0
588
589    def run_ci_vcs(self, test):
590        all_tests = {
591            "cputest": self.__get_ci_cputest,
592            "riscv-tests": self.__get_ci_rvtest,
593            "misc-tests": self.__get_ci_misc,
594            "mc-tests": self.__get_ci_mc,
595            "nodiff-tests": self.__get_ci_nodiff,
596            "rvh-tests": self.__get_ci_rvhtest,
597            "microbench": self.__am_apps_path,
598            "coremark": self.__am_apps_path,
599            "coremark-1-iteration": self.__am_apps_path,
600            "rvv-bench": self.__get_ci_rvvbench,
601            "rvv-test": self.__get_ci_rvvtest,
602            "f16_test": self.__get_ci_F16test,
603            "zcb-test": self.__get_ci_zcbtest
604        }
605        for target in all_tests.get(test, self.__get_ci_workloads)(test):
606            print(target)
607            ret = self.run_simv(target)
608            if ret:
609                if self.args.default_wave_home != self.args.wave_home:
610                    print("copy wave file to " + self.args.wave_home)
611                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.fsdb $WAVE_HOME")
612                    self.__exec_cmd(f"cp $NOOP_HOME/build/simv $WAVE_HOME")
613                    self.__exec_cmd(f"cp $NOOP_HOME/build/rtl/SimTop.v $WAVE_HOME")
614                    self.__exec_cmd(f"cp $NOOP_HOME/build/*.db $WAVE_HOME")
615                return ret
616        return 0
617
618def get_free_cores(n):
619    numa_re = re.compile(r'.*numactl +.*-C +([0-9]+)-([0-9]+).*')
620    while True:
621        disable_cores = []
622        for proc in psutil.process_iter():
623            try:
624                joint = ' '.join(proc.cmdline())
625                numa_match = numa_re.match(joint)
626                if numa_match and 'ssh' not in proc.name():
627                    disable_cores.extend(range(int(numa_match.group(1)), int(numa_match.group(2)) + 1))
628            except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
629                pass
630        num_logical_core = psutil.cpu_count(logical=False)
631        core_usage = psutil.cpu_percent(interval=1, percpu=True)
632        num_window = num_logical_core // n
633        for i in range(num_window):
634            if set(disable_cores) & set(range(i * n, i * n + n)):
635                continue
636            window_usage = core_usage[i * n : i * n + n]
637            if sum(window_usage) < 30 * n and True not in map(lambda x: x > 90, window_usage):
638                return (((i * n) % num_logical_core) // (num_logical_core // 2), i * n, i * n + n - 1)
639        print(f"No free {n} cores found. CPU usage: {core_usage}\n")
640        time.sleep(random.uniform(1, 60))
641
642if __name__ == "__main__":
643    parser = argparse.ArgumentParser(description='Python wrapper for XiangShan')
644    parser.add_argument('workload', nargs='?', type=str, default="",
645                        help='input workload file in binary format')
646    # actions
647    parser.add_argument('--build', action='store_true', help='build XS emu')
648    parser.add_argument('--generate', action='store_true', help='generate XS verilog')
649    parser.add_argument('--vcs-gen', action='store_true', help='generate XS sim verilog for vcs')
650    parser.add_argument('--vcs-build', action='store_true', help='build XS simv')
651    parser.add_argument('--ci', nargs='?', type=str, const="", help='run CI tests')
652    parser.add_argument('--ci-vcs', nargs='?', type=str, const="", help='run CI tests on simv')
653    parser.add_argument('--clean', action='store_true', help='clean up XiangShan CI workspace')
654    parser.add_argument('--timeout', nargs='?', type=int, default=None, help='timeout (in seconds)')
655    # environment variables
656    parser.add_argument('--nemu', nargs='?', type=str, help='path to nemu')
657    parser.add_argument('--am', nargs='?', type=str, help='path to nexus-am')
658    parser.add_argument('--dramsim3', nargs='?', type=str, help='path to dramsim3')
659    parser.add_argument('--rvtest', nargs='?', type=str, help='path to riscv-tests')
660    parser.add_argument('--wave-dump', nargs='?', type=str , help='path to dump wave')
661    # chisel arguments
662    parser.add_argument('--enable-log', action='store_true', help='enable log')
663    parser.add_argument('--num-cores', type=int, help='number of cores')
664    # makefile arguments
665    parser.add_argument('--release', action='store_true', help='enable release')
666    parser.add_argument('--spike', action='store_true', help='enable spike diff')
667    parser.add_argument('--with-dramsim3', action='store_true', help='enable dramsim3')
668    parser.add_argument('--threads', nargs='?', type=int, help='number of emu threads')
669    parser.add_argument('--trace', action='store_true', help='enable vcd waveform')
670    parser.add_argument('--trace-fst', action='store_true', help='enable fst waveform')
671    parser.add_argument('--config', nargs='?', type=str, help='config')
672    parser.add_argument('--emu-optimize', nargs='?', type=str, help='verilator optimization letter')
673    parser.add_argument('--xprop', action='store_true', help='enable xprop for vcs')
674    # emu arguments
675    parser.add_argument('--numa', action='store_true', help='use numactl')
676    parser.add_argument('--diff', nargs='?', default="./ready-to-run/riscv64-nemu-interpreter-so", type=str, help='nemu so')
677    parser.add_argument('--max-instr', nargs='?', type=int, help='max instr')
678    parser.add_argument('--disable-fork', action='store_true', help='disable lightSSS')
679    parser.add_argument('--no-diff', action='store_true', help='disable difftest')
680    parser.add_argument('--ram-size', nargs='?', type=str, help='manually set simulation memory size (8GB by default)')
681    parser.add_argument('--gcpt-restore-bin', type=str, default="", help="specify the bin used to restore from gcpt")
682    # both makefile and emu arguments
683    parser.add_argument('--no-db', action='store_true', help='disable chiseldb dump')
684    parser.add_argument('--pgo', nargs='?', type=str, help='workload for pgo (null to disable pgo)')
685    parser.add_argument('--pgo-max-cycle', nargs='?', default=400000, type=int, help='maximun cycle to train pgo')
686    parser.add_argument('--pgo-emu-args', nargs='?', default='--no-diff', type=str, help='emu arguments for pgo')
687    parser.add_argument('--llvm-profdata', nargs='?', type=str, help='corresponding llvm-profdata command of clang to compile emu, do not set with GCC')
688
689    args = parser.parse_args()
690
691    xs = XiangShan(args)
692    ret = xs.run(args)
693
694    sys.exit(ret)
695