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