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