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