1# Copyright (c) 2020 Synopsys.
2#
3# SPDX-License-Identifier: Apache-2.0
4
5'''Runners for Synopsys Metaware Debugger(mdb).'''
6
7
8import os
9import shutil
10from os import path
11
12from runners.core import RunnerCaps, ZephyrBinaryRunner
13
14
15# normally we should create class with common functionality inherited from
16# ZephyrBinaryRunner and inherit MdbNsimBinaryRunner and MdbHwBinaryRunner
17# from it. However as we do lookup for runners with
18# ZephyrBinaryRunner.__subclasses__() such sub-sub-classes won't be found.
19# So, we move all common functionality to helper functions instead.
20def is_simulation_run(mdb_runner):
21    return mdb_runner.nsim_args != ''
22
23def is_hostlink_used(mdb_runner):
24    return mdb_runner.build_conf.getboolean('CONFIG_UART_HOSTLINK')
25
26def is_flash_cmd_need_exit_immediately(mdb_runner):
27    if is_simulation_run(mdb_runner):
28        # for nsim, we can't run and quit immediately
29        return False
30
31    # if hostlink is used we can't run and quit immediately, as we still need MDB process
32    # attached to process hostlink IO
33    return not is_hostlink_used(mdb_runner)
34
35def smp_core_order(mdb_runner, id):
36    if is_simulation_run(mdb_runner):
37        # for simulation targets we start cores in direct order
38        # (core 0 first, core 1 second, etc...)
39        # otherwise we face mismatch arcnum (code ID) with ARConnect ID
40        # and core ID in instruction traces
41        return id
42    else:
43        # for HW targets we want to start the primary core last,
44        # to avoid ARConnect initialization interfere
45        # with secondary cores startup - so we reverse start order
46        return mdb_runner.cores - 1 - id
47
48def mdb_do_run(mdb_runner, command):
49    commander = "mdb64"
50
51    mdb_runner.require(commander)
52
53    mdb_basic_options = ['-nooptions', '-nogoifmain', '-toggle=include_local_symbols=1']
54
55    # remove previous .sc.project folder which has temporary settings
56    # for MDB. This is useful for troubleshooting situations with
57    # unexpected behavior of the debugger
58    mdb_cfg_dir = path.join(mdb_runner.build_dir, '.sc.project')
59    if path.exists(mdb_cfg_dir):
60        shutil.rmtree(mdb_cfg_dir)
61
62    # nsim
63    if is_simulation_run(mdb_runner):
64        mdb_target = ['-nsim', '@' + mdb_runner.nsim_args]
65    # hardware target
66    else:
67        if mdb_runner.jtag == 'digilent':
68            mdb_target = ['-digilent']
69            if mdb_runner.dig_device:
70                mdb_target += [mdb_runner.dig_device]
71        else:
72            # \todo: add support of other debuggers
73            raise ValueError(f'unsupported jtag adapter {mdb_runner.jtag}')
74
75    if command == 'flash':
76        if is_flash_cmd_need_exit_immediately(mdb_runner):
77            mdb_run = ['-run', '-cmd=-nowaitq run', '-cmd=quit', '-cl']
78        else:
79            mdb_run = ['-run', '-cl']
80    elif command == 'debug':
81        # use mdb gui to debug
82        mdb_run = ['-OKN']
83
84    if mdb_runner.cores == 1:
85        # single core's mdb command is different with multicores
86        mdb_cmd = [commander] + mdb_basic_options + mdb_target + mdb_run + [mdb_runner.elf_name]
87    elif 1 < mdb_runner.cores <= 12:
88        mdb_multifiles = '-multifiles='
89        for i in range(mdb_runner.cores):
90            mdb_sub_cmd = [commander] + [f'-pset={i + 1}', f'-psetname=core{i}']
91            # -prop=download=2 is used for SMP application debug, only the 1st core
92            # will download the shared image.
93            if i > 0:
94                mdb_sub_cmd += ['-prop=download=2']
95            mdb_sub_cmd += mdb_basic_options + mdb_target + [mdb_runner.elf_name]
96            mdb_runner.check_call(mdb_sub_cmd, cwd=mdb_runner.build_dir)
97            mdb_multifiles += f'{"" if i == 0 else ","}core{smp_core_order(mdb_runner, i)}'
98
99        # to enable multi-core aware mode for use with the MetaWare debugger,
100        # need to set the NSIM_MULTICORE environment variable to a non-zero value
101        if is_simulation_run(mdb_runner):
102            os.environ["NSIM_MULTICORE"] = '1'
103
104        mdb_cmd = [commander] + [mdb_multifiles] + mdb_run
105    else:
106        raise ValueError(f'unsupported cores {mdb_runner.cores}')
107
108    mdb_runner.call(mdb_cmd, cwd=mdb_runner.build_dir)
109
110
111class MdbNsimBinaryRunner(ZephyrBinaryRunner):
112    '''Runner front-end for nSIM via mdb.'''
113
114    def __init__(self, cfg, cores=1, nsim_args=''):
115        super().__init__(cfg)
116        self.jtag = ''
117        self.cores = int(cores)
118        if nsim_args != '':
119            self.nsim_args = path.join(cfg.board_dir, 'support', nsim_args)
120        else:
121            self.nsim_args = ''
122        self.elf_name = cfg.elf_file
123        self.build_dir = cfg.build_dir
124        self.dig_device = ''
125
126    @classmethod
127    def name(cls):
128        return 'mdb-nsim'
129
130    @classmethod
131    def capabilities(cls):
132        return RunnerCaps(commands={'flash', 'debug'})
133
134    @classmethod
135    def do_add_parser(cls, parser):
136        parser.add_argument('--cores', default=1,
137                            help='''choose the cores that target has, e.g.
138                                    --cores=1''')
139        parser.add_argument('--nsim_args', default='',
140                            help='''if given, arguments for nsim simulator
141                                 through mdb which should be in
142                                 <board_dir>/support, e.g. --nsim-args=
143                                 mdb_em.args''')
144
145    @classmethod
146    def do_create(cls, cfg, args):
147        return MdbNsimBinaryRunner(
148            cfg,
149            cores=args.cores,
150            nsim_args=args.nsim_args)
151
152    def do_run(self, command, **kwargs):
153        mdb_do_run(self, command)
154
155
156class MdbHwBinaryRunner(ZephyrBinaryRunner):
157    '''Runner front-end for mdb.'''
158
159    def __init__(self, cfg, cores=1, jtag='digilent', dig_device=''):
160        super().__init__(cfg)
161        self.jtag = jtag
162        self.cores = int(cores)
163        self.nsim_args = ''
164        self.elf_name = cfg.elf_file
165        if dig_device != '':
166            self.dig_device = '-prop=dig_device=' + dig_device
167        else:
168            self.dig_device = ''
169        self.build_dir = cfg.build_dir
170
171    @classmethod
172    def name(cls):
173        return 'mdb-hw'
174
175    @classmethod
176    def capabilities(cls):
177        return RunnerCaps(commands={'flash', 'debug'})
178
179    @classmethod
180    def do_add_parser(cls, parser):
181        parser.add_argument('--jtag', default='digilent',
182                            help='''choose the jtag interface for hardware
183                                    targets, e.g. --jtag=digilent for digilent
184                                    jtag adapter''')
185        parser.add_argument('--cores', default=1,
186                            help='''choose the number of cores that target has,
187                                    e.g. --cores=1''')
188        parser.add_argument('--dig-device', default='',
189                            help='''choose the specific digilent device to
190                             connect, this is useful when multiple
191                             targets are connected''')
192
193    @classmethod
194    def do_create(cls, cfg, args):
195        return MdbHwBinaryRunner(
196            cfg,
197            cores=args.cores,
198            jtag=args.jtag,
199            dig_device=args.dig_device)
200
201    def do_run(self, command, **kwargs):
202        mdb_do_run(self, command)
203