simulators.py 30.7 KB
Newer Older
Antoine Kaufmann's avatar
Antoine Kaufmann committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Copyright 2021 Max Planck Institute for Software Systems, and
# National University of Singapore
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

23
24
25
# Allow own class to be used as type for a method's argument
from __future__ import annotations

26
import math
27
28
import typing as tp

29
30
from simbricks.orchestration.experiment.experiment_environment import ExpEnv
from simbricks.orchestration.nodeconfig import NodeConfig
31

32

33
class Simulator(object):
34
35
    """Base class for all simulators."""

36
37
    def __init__(self):
        self.extra_deps = []
Jonas Kaufmann's avatar
Jonas Kaufmann committed
38
        self.name = ''
39

40
    def resreq_cores(self):
41
42
43
44
45
        """
        Number of cores this simulator requires during execution.

        This is used for scheduling multiple runs and experiments.
        """
46
47
48
        return 1

    def resreq_mem(self):
49
50
51
52
53
        """
        Number of memory in MB this simulator requires during execution.

        This is used for scheduling multiple runs and experiments.
        """
54
55
        return 64

Jonas Kaufmann's avatar
Jonas Kaufmann committed
56
57
58
59
    def full_name(self):
        """Full name of the simulator."""
        return ''

60
    # pylint: disable=unused-argument
61
    def prep_cmds(self, env: ExpEnv) -> tp.List[str]:
62
        """Commands to prepare execution of this simulator."""
63
64
        return []

65
    # pylint: disable=unused-argument
66
    def run_cmd(self, env: ExpEnv) -> tp.Optional[str]:
67
        """Command to execute this simulator."""
68
        return None
69

Jonas Kaufmann's avatar
Jonas Kaufmann committed
70
    def dependencies(self) -> tp.List[Simulator]:
71
        """Other simulators to execute before this one."""
72
73
74
        return []

    # Sockets to be cleaned up
75
    # pylint: disable=unused-argument
76
    def sockets_cleanup(self, env: ExpEnv):
77
78
79
        return []

    # sockets to wait for indicating the simulator is ready
80
    # pylint: disable=unused-argument
81
    def sockets_wait(self, env: ExpEnv):
82
83
84
85
86
87
88
89
        return []

    def start_delay(self):
        return 5

    def wait_terminate(self):
        return False

90

91
class PCIDevSim(Simulator):
92
93
94
95
96
97
    """Base class for PCIe device simulators."""

    def __init__(self):
        super().__init__()

        self.sync_mode = 0
98
99
        """Synchronization mode. 0 is running unsynchronized, 1 synchronized.
        Depending on the concrete simulator, there may be additional modes."""
100
        self.start_tick = 0
101
102
103
104
105
        """The timestamp at which to start the simulation. This is useful when
        the simulator is only attached at a later point in time and needs to
        synchronize with connected simulators. For example, this could be used
        when taking checkpoints to only attach certain simulators after the
        checkpoint has been taken."""
106
        self.sync_period = 500
107
108
        """Period in nanoseconds of sending synchronization messages from this
        device to connected components."""
109
        self.pci_latency = 500
110
111
        """Latency in nanoseconds for sending messages to components connected
        via PCI."""
112
113
114
115

    def full_name(self):
        return 'dev.' + self.name

116
117
118
    def is_nic(self):
        return False

119
120
121
122
123
124
125
126
    def sockets_cleanup(self, env):
        return [env.dev_pci_path(self), env.dev_shm_path(self)]

    def sockets_wait(self, env):
        return [env.dev_pci_path(self)]


class NICSim(PCIDevSim):
127
128
129
130
131
132
133
134
    """Base class for NIC simulators."""

    def __init__(self):
        super().__init__()

        self.network: tp.Optional[NetSim] = None
        self.mac: tp.Optional[str] = None
        self.eth_latency = 500
135
136
        """Ethernet latency in nanoseconds from this NIC to the network
        component."""
137

Jonas Kaufmann's avatar
Jonas Kaufmann committed
138
    def set_network(self, net: NetSim):
139
        """Connect this NIC to a network simulator."""
140
141
142
        self.network = net
        net.nics.append(self)

143
    def basic_args(self, env, extra=None):
144
145
        cmd = (
            f'{env.dev_pci_path(self)} {env.nic_eth_path(self)}'
146
147
            f' {env.dev_shm_path(self)} {self.sync_mode} {self.start_tick}'
            f' {self.sync_period} {self.pci_latency} {self.eth_latency}'
148
        )
149
150
        if self.mac is not None:
            cmd += ' ' + (''.join(reversed(self.mac.split(':'))))
151
152
153
154

        if extra is not None:
            cmd += ' ' + extra
        return cmd
155

156
    def basic_run_cmd(self, env, name, extra=None):
157
        cmd = f'{env.repodir}/sims/nic/{name} {self.basic_args(env, extra)}'
158
159
        return cmd

160
    def full_name(self):
161
        return 'nic.' + self.name
162

163
164
165
    def is_nic(self):
        return True

166
    def sockets_cleanup(self, env):
167
        return super().sockets_cleanup(env) + [env.nic_eth_path(self)]
168
169

    def sockets_wait(self, env):
170
        return super().sockets_wait(env) + [env.nic_eth_path(self)]
171

Jonas Kaufmann's avatar
Jonas Kaufmann committed
172

173
class NetSim(Simulator):
174
    """Base class for network simulators."""
175
176
177
178
179
180

    def __init__(self):
        super().__init__()

        self.opt = ''
        self.sync_mode = 0
181
182
        """Synchronization mode. 0 is running unsynchronized, 1 synchronized.
        Depending on the concrete simulator, there may be additional modes."""
183
        self.sync_period = 500
184
185
        """Synchronization period in nanoseconds from this network to connected
        components."""
186
        self.eth_latency = 500
187
188
        """Ethernet latency in nanoseconds from this network to connected
        components."""
189
190
191
192
        self.nics: list[NICSim] = []
        self.hosts_direct: list[HostSim] = []
        self.net_listen: list[NetSim] = []
        self.net_connect: list[NetSim] = []
193
194

    def full_name(self):
195
        return 'net.' + self.name
196

197
    def connect_network(self, net: NetSim):
198
        """Connect this network to the listening peer `net`"""
199
200
201
        net.net_listen.append(self)
        self.net_connect.append(net)

Jonas Kaufmann's avatar
Jonas Kaufmann committed
202
    def connect_sockets(self, env: ExpEnv) -> tp.List[tp.Tuple[Simulator, str]]:
203
204
205
        sockets = []
        for n in self.nics:
            sockets.append((n, env.nic_eth_path(n)))
206
207
        for n in self.net_connect:
            sockets.append((n, env.n2n_eth_path(n, self)))
208
209
        for h in self.hosts_direct:
            sockets.append((h, env.net2host_eth_path(self, h)))
210
211
        return sockets

212
    def listen_sockets(self, env: ExpEnv):
213
214
215
216
217
        listens = []
        for net in self.net_listen:
            listens.append((net, env.n2n_eth_path(self, net)))
        return listens

218
    def dependencies(self):
219
        return self.nics + self.net_connect + self.hosts_direct
220

221
    def sockets_cleanup(self, env: ExpEnv):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
222
        return [s for (_, s) in self.listen_sockets(env)]
223

224
    def sockets_wait(self, env: ExpEnv):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
225
        return [s for (_, s) in self.listen_sockets(env)]
226

227

228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# FIXME: Class hierarchy is broken here as an ugly hack
class MemDevSim(NICSim):
    """Base class for memory device simulators."""

    def __init__(self):
        super().__init__()

        self.mem_latency = 500
        self.addr = 0xe000000000000000
        self.size = 1024 * 1024 * 1024  # 1GB
        self.as_id = 0

    def full_name(self):
        return 'mem.' + self.name

    def sockets_cleanup(self, env):
        return [env.dev_mem_path(self), env.dev_shm_path(self)]

    def sockets_wait(self, env):
        return [env.dev_mem_path(self)]


class NetMemSim(NICSim):
    """Base class for netork memory simulators."""

    def __init__(self):
        super().__init__()

        self.addr = 0xe000000000000000
        self.size = 1024 * 1024 * 1024  # 1GB
        self.as_id = 0

    def full_name(self):
        return 'netmem.' + self.name

    def sockets_cleanup(self, env):
        return [env.nic_eth_path(self), env.dev_shm_path(self)]

    def sockets_wait(self, env):
        return [env.nic_eth_path(self)]


270
class HostSim(Simulator):
271
    """Base class for host simulators."""
272

273
    def __init__(self, node_config: NodeConfig):
274
        super().__init__()
275
        self.node_config = node_config
276
        """System configuration for this simulated host. """
277
278
        self.wait = False
        """
279
280
281
        `True` - Wait for this simulator to finish execution. `False` - Don't
        wait and instead shutdown the simulator as soon as all other awaited
        simulators have completed execution.
282
283
        """
        self.sleep = 0
284
        self.cpu_freq = '4GHz'
285
286

        self.sync_mode = 0
287
288
        """Synchronization mode. 0 is running unsynchronized, 1 synchronized.
        Depending on the concrete simulator, there may be additional modes."""
289
        self.sync_period = 500
290
291
        """Period in nanoseconds of sending synchronization messages from this
        device to connected components."""
292
        self.pci_latency = 500
293
294
        """Latency in nanoseconds for sending messages to components connected
        via PCIe."""
295
        self.mem_latency = 500
296
297
        """Latency in nanoseconds for sending messages to components connected
        via Ethernet."""
298

299
        self.pcidevs: tp.List[PCIDevSim] = []
300
        self.net_directs: tp.List[NetSim] = []
301
        self.memdevs: tp.List[MemDevSim] = []
302

303
304
305
306
    @property
    def nics(self):
        return filter(lambda pcidev: pcidev.is_nic(), self.pcidevs)

307
308
309
310
    def full_name(self):
        return 'host.' + self.name

    def add_nic(self, dev: NICSim):
311
        """Add a NIC to this host."""
312
313
        self.add_pcidev(dev)

314
    def add_pcidev(self, dev: PCIDevSim):
315
        """Add a PCIe device to this host."""
316
317
318
        dev.name = self.name + '.' + dev.name
        self.pcidevs.append(dev)

319
320
321
322
    def add_memdev(self, dev: MemDevSim):
        dev.name = self.name + '.' + dev.name
        self.memdevs.append(dev)

323
    def add_netdirect(self, net: NetSim):
324
        """Add a direct connection to a network to this host."""
325
326
327
        net.hosts_direct.append(self)
        self.net_directs.append(net)

328
329
330
331
332
333
    def dependencies(self):
        deps = []
        for dev in self.pcidevs:
            deps.append(dev)
            if isinstance(dev, NICSim):
                deps.append(dev.network)
334
335
        for dev in self.memdevs:
            deps.append(dev)
336
337
338
339
340
341
        return deps

    def wait_terminate(self):
        return self.wait


342
class QemuHost(HostSim):
343
344
    """Qemu host simulator."""

345
346
    def __init__(self, node_config: NodeConfig):
        super().__init__(node_config)
347
348

        self.sync = False
349
        """"Whether to synchronize with attached simulators."""
350

351
    def resreq_cores(self):
352
353
354
355
        if self.sync:
            return 1
        else:
            return self.node_config.cores + 1
356
357

    def resreq_mem(self):
Hejing Li's avatar
Hejing Li committed
358
        return 8192
359
360

    def prep_cmds(self, env):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
361
362
        return [
            f'{env.qemu_img_path} create -f qcow2 -o '
363
            f'backing_file="{env.hd_path(self.node_config.disk_image)}" '
Jonas Kaufmann's avatar
Jonas Kaufmann committed
364
365
            f'{env.hdcopy_path(self)}'
        ]
366
367

    def run_cmd(self, env):
368
        accel = ',accel=kvm:tcg' if not self.sync else ''
369
370
371
372
373
        if self.node_config.kcmd_append:
            kcmd_append = ' ' + self.node_config.kcmd_append
        else:
            kcmd_append = ''

Jonas Kaufmann's avatar
Jonas Kaufmann committed
374
375
        cmd = (
            f'{env.qemu_path} -machine q35{accel} -serial mon:stdio '
376
            '-cpu Skylake-Server -display none -nic none '
377
378
379
            f'-kernel {env.qemu_kernel_path} '
            f'-drive file={env.hdcopy_path(self)},if=ide,index=0,media=disk '
            f'-drive file={env.cfgtar_path(self)},if=ide,index=1,media=disk,'
Jonas Kaufmann's avatar
Jonas Kaufmann committed
380
            'driver=raw '
381
            '-append "earlyprintk=ttyS0 console=ttyS0 root=/dev/sda1 '
382
            f'init=/home/ubuntu/guestinit.sh rw{kcmd_append}" '
Jonas Kaufmann's avatar
Jonas Kaufmann committed
383
384
            f'-m {self.node_config.memory} -smp {self.node_config.cores} '
        )
385
386

        if self.sync:
387
388
389
390
391
392
            unit = self.cpu_freq[-3:]
            if unit.lower() == 'ghz':
                base = 0
            elif unit.lower() == 'mhz':
                base = 3
            else:
393
                raise ValueError('cpu frequency specified in unsupported unit')
394
395
396
            num = float(self.cpu_freq[:-3])
            shift = base - int(math.ceil(math.log(num, 2)))

397
            cmd += f' -icount shift={shift},sleep=off '
398

399
        for dev in self.pcidevs:
400
            cmd += f'-device simbricks-pci,socket={env.dev_pci_path(dev)}'
401
402
403
404
405
406
407
408
            if self.sync:
                cmd += ',sync=on'
                cmd += f',pci-latency={self.pci_latency}'
                cmd += f',sync-period={self.sync_period}'
            else:
                cmd += ',sync=off'
            cmd += ' '

409
        # qemu does not currently support net direct ports
410
        assert len(self.net_directs) == 0
411
412
        # qemu does not currently support mem device ports
        assert len(self.memdevs) == 0
413
414
        return cmd

Jonas Kaufmann's avatar
Jonas Kaufmann committed
415

416
class Gem5Host(HostSim):
417
    """Gem5 host simulator."""
418

419
420
421
    def __init__(self, node_config: NodeConfig):
        node_config.sim = 'gem5'
        super().__init__(node_config)
422
423
424
        self.cpu_type_cp = 'X86KvmCPU'
        self.cpu_type = 'TimingSimpleCPU'
        self.sys_clock = '1GHz'
425
426
        self.extra_main_args = []
        self.extra_config_args = []
427
        self.variant = 'fast'
428

429
430
431
432
433
434
    def resreq_cores(self):
        return 1

    def resreq_mem(self):
        return 4096

435
436
    def prep_cmds(self, env):
        return [f'mkdir -p {env.gem5_cpdir(self)}']
437
438
439
440
441
442

    def run_cmd(self, env):
        cpu_type = self.cpu_type
        if env.create_cp:
            cpu_type = self.cpu_type_cp

443
        cmd = f'{env.gem5_path(self.variant)} --outdir={env.gem5_outdir(self)} '
444
        cmd += ' '.join(self.extra_main_args)
Jonas Kaufmann's avatar
Jonas Kaufmann committed
445
446
        cmd += (
            f' {env.gem5_py_path} --caches --l2cache --l3cache '
447
            '--l1d_size=32kB --l1i_size=32kB --l2_size=2MB --l3_size=32MB '
448
            '--l1d_assoc=8 --l1i_assoc=8 --l2_assoc=4 --l3_assoc=16 '
449
450
            f'--cacheline_size=64 --cpu-clock={self.cpu_freq}'
            f' --sys-clock={self.sys_clock} '
451
452
            f'--checkpoint-dir={env.gem5_cpdir(self)} '
            f'--kernel={env.gem5_kernel_path} '
453
            f'--disk-image={env.hd_raw_path(self.node_config.disk_image)} '
454
            f'--disk-image={env.cfgtar_path(self)} '
455
            f'--cpu-type={cpu_type} --mem-size={self.node_config.memory}MB '
456
            f'--num-cpus={self.node_config.cores} '
Jonas Kaufmann's avatar
Jonas Kaufmann committed
457
458
            '--ddio-enabled --ddio-way-part=8 --mem-type=DDR4_2400_16x4 '
        )
459

460
461
462
        if self.node_config.kcmd_append:
            cmd += f'--command-line-append="{self.node_config.kcmd_append}" '

463
464
        if env.create_cp:
            cmd += '--max-checkpoints=1 '
465

466
        if env.restore_cp:
467
            cmd += '-r 1 '
468

469
        for dev in self.pcidevs:
Jonas Kaufmann's avatar
Jonas Kaufmann committed
470
471
472
473
474
            cmd += (
                f'--simbricks-pci=connect:{env.dev_pci_path(dev)}'
                f':latency={self.pci_latency}ns'
                f':sync_interval={self.sync_period}ns'
            )
475
            if cpu_type == 'TimingSimpleCPU':
476
                cmd += ':sync'
Jonas Kaufmann's avatar
Jonas Kaufmann committed
477
            cmd += ' '
478

479
480
481
482
483
484
485
486
487
488
489
        for dev in self.memdevs:
            cmd += (
                f'--simbricks-mem={dev.size}@{dev.addr}@{dev.as_id}@'
                f'connect:{env.dev_mem_path(dev)}'
                f':latency={self.mem_latency}ns'
                f':sync_interval={self.sync_period}ns'
            )
            if cpu_type == 'TimingSimpleCPU':
                cmd += ':sync'
            cmd += ' '

490
        for net in self.net_directs:
Jonas Kaufmann's avatar
Jonas Kaufmann committed
491
492
493
494
495
496
497
            cmd += (
                '--simbricks-eth-e1000=listen'
                f':{env.net2host_eth_path(net, self)}'
                f':{env.net2host_shm_path(net, self)}'
                f':latency={net.eth_latency}ns'
                f':sync_interval={net.sync_period}ns'
            )
498
499
            if cpu_type == 'TimingSimpleCPU':
                cmd += ':sync'
Jonas Kaufmann's avatar
Jonas Kaufmann committed
500
            cmd += ' '
501
502

        cmd += ' '.join(self.extra_config_args)
503
504
505
        return cmd


506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
class SimicsHost(HostSim):
    """Simics host simulator."""

    def __init__(self, node_config: NodeConfig):
        super().__init__(node_config)
        node_config.sim = 'simics'

        self.cpu_class = 'x86-cooper-lake'
        """Simics CPU class. Can be obtained by running `list-classes substr =
        processor_` inside Simics."""
        self.cpu_freq = 4000  # TODO Don't hide attribute in super class
        """CPU frequency in MHz"""
        self.timing = False
        """Whether to run Simics in a more precise timing mode. This adds a
        cache model."""
        self.append_cmdline: tp.List[str] = []
        """Additional parameters to append on the command-line when invoking
        Simics."""
        self.interactive = False
        """Whether to launch Simics in interactive GUI mode. This is helpful for
        debugging, e.g. enabling log messages in the mid of the simulation."""
        self.debug_messages = False
        """Whether to enable debug messages of SimBricks adapter devices."""

    def resreq_cores(self):
        return 2

    def resreq_mem(self):
        return self.node_config.memory

    def run_cmd(self, env):
        if self.node_config.kcmd_append:
            raise RuntimeError(
                'Appending kernel command-line not yet implemented.'
            )

        if self.interactive and not env.create_cp:
            cmd = f'{env.simics_gui_path} -q '
        else:
            cmd = f'{env.simics_path} -q -batch-mode -werror '

        if env.restore_cp:
            # restore checkpoint
            cmd += f'-e \'read-configuration {env.simics_cpfile(self)}\' '
        else:
            # initialize simulated machine
            cmd += (
                '-e \'run-command-file '
                f'{env.simics_qsp_modern_core_path} '
                f'disk0_image = {env.hd_raw_path(self.node_config.disk_image)} '
                f'disk1_image = {env.cfgtar_path(self)} '
                f'cpu_comp_class = {self.cpu_class} '
                f'freq_mhz = {self.cpu_freq} '
                f'num_cores = {self.node_config.cores} '
                f'num_threads = {self.node_config.threads} '
561
562
                f'memory_megs = {self.node_config.memory} '
                'create_network = FALSE\' '
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
            )

        if env.create_cp:
            # stop simulation when encountering special checkpoint string on
            # serial console
            cmd += (
                '-e \'bp.console_string.break board.serconsole.con '
                '"ready to checkpoint"\' '
            )
            # run simulation
            cmd += '-e run '
            # create checkpoint
            cmd += f'-e \'write-configuration {env.simics_cpfile(self)}\' '
            return cmd

        if self.timing:
            # Add the cache model. Note that the caches aren't warmed up during
            # the boot process. The reason is that when later adding the memory
            # devices, we change the mapped memory. The cycle staller doesn't
            # like this and will SEGFAULT.
            #
            # The cache model doesn't store any memory contents and therefore
            # doesn't answer any memory transactions. It only inserts CPU stall
            # cycles on each cache level and can be queried for statistics as
            # well as which addresses are cached.
            #
            # Read penalties are based on https://www.7-cpu.com/cpu/Skylake.html
            cmd += (
                '-e \'new-cycle-staller name = cs0 '
                'stall-interval = 10000\' '
            )
            cmd += (
                '-e \'new-simple-cache-tool name = cachetool '
                'cycle-staller = cs0 -connect-all\' '
            )
            cmd += (
                '-e \'cachetool.add-l1i-cache name = l1i line-size = 64 '
                'sets = 64 ways = 8\' '
            )
            cmd += (
                '-e \'cachetool.add-l1d-cache name = l1d line-size = 64 '
                'sets = 64 ways = 8 -ip-read-prefetcher '
                'prefetch-additional = 1 read-penalty = 4\' '
            )
            cmd += (
                '-e \'cachetool.add-l2-cache name = l2 line-size = 64 '
                'sets = 8192 ways = 4 -prefetch-adjacent '
                'prefetch-additional = 4 read-penalty = 12\' '
            )
            cmd += (
                '-e \'cachetool.add-l3-cache name = l3 line-size = 64 '
                'sets = 32768 ways = 16 read-penalty = 42\' '
            )

        # Only simulate one cycle per CPU and then switch to the next. This is
        # necessary for the synchronization of the SimBricks adapter with all
        # the CPUs to work properly.
        cmd += '-e \'set-time-quantum 1\' '

        if self.memdevs:
            cmd += '-e \'load-module simbricks_mem_comp\' '

        for memdev in self.memdevs:
            cmd += (
                f'-e \'$mem = (new-simbricks-mem-comp '
                f'socket = "{env.dev_mem_path(memdev)}" '
                f'mem_latency = {self.mem_latency} '
                f'sync_period = {self.sync_period})\' '
            )
            cmd += (
                f'-e \'board.mb.dram_space.add-map $mem.simbricks_mem_dev '
                f'{memdev.addr:#x} {memdev.size:#x}\' '
            )
            if self.debug_messages:
                cmd += '-e \'$mem.log-level 3\' '

639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
        if self.pcidevs:
            cmd += '-e \'load-module simbricks_pcie_comp\' '

        i = 0
        for pcidev in self.pcidevs:
            cmd += (
                f'-e \'$pci = (create-simbricks-pcie-comp '
                f'socket = "{env.dev_pci_path(pcidev)}" '
                f'pci_latency = {self.pci_latency} '
                f'sync_period = {self.sync_period})\' '
            )
            cmd += f'-e \'connect board.mb.nb.pci_slot[{i}] $pci.pci_bus\' '
            cmd += '-e instantiate-components '
            if self.debug_messages:
                cmd += '-e \'$pci.log-level 3\' '
            i += 1

656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
        for param in self.append_cmdline:
            cmd += f'{param} '

        # The simulation keeps running when the host powers off. A log message
        # indicates the event when the machine is powering off. We place a
        # breakpoint on that log message, which will terminate Simics due to the
        # use of `-batch-mode`.
        cmd += (
            '-e \'bp.log.break object=board.mb.sb.lpc.bank.acpi_io_regs '
            'substr="Sleep state is unimplemented" type=unimpl\' '
        )

        return cmd + '-e run'


671
class CorundumVerilatorNIC(NICSim):
672
673
674
675

    def __init__(self):
        super().__init__()
        self.clock_freq = 250  # MHz
676

677
678
679
680
681
    def resreq_mem(self):
        # this is a guess
        return 512

    def run_cmd(self, env):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
682
683
684
685
        return self.basic_run_cmd(
            env, '/corundum/corundum_verilator', str(self.clock_freq)
        )

686
687

class CorundumBMNIC(NICSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
688

689
    def run_cmd(self, env):
Hejing Li's avatar
Hejing Li committed
690
        return self.basic_run_cmd(env, '/corundum_bm/corundum_bm')
691

Jonas Kaufmann's avatar
Jonas Kaufmann committed
692

693
class I40eNIC(NICSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
694

695
    def run_cmd(self, env):
Hejing Li's avatar
Hejing Li committed
696
        return self.basic_run_cmd(env, '/i40e_bm/i40e_bm')
697

Jonas Kaufmann's avatar
Jonas Kaufmann committed
698

699
class E1000NIC(NICSim):
700
701
702
703

    def __init__(self):
        super().__init__()
        self.debug = False
Jonas Kaufmann's avatar
Jonas Kaufmann committed
704

705
    def run_cmd(self, env):
706
707
708
709
        cmd = self.basic_run_cmd(env, '/e1000_gem5/e1000_gem5')
        if self.debug:
            cmd = 'env E1000_DEBUG=1 ' + cmd
        return cmd
710

Jonas Kaufmann's avatar
Jonas Kaufmann committed
711

712
713
714
715
class MultiSubNIC(NICSim):

    def __init__(self, mn):
        super().__init__()
716
        self.multinic = mn
717
718
719
720
721
722
723
724
725
726

    def full_name(self):
        return self.multinic.full_name() + '.' + self.name

    def dependencies(self):
        return super().dependencies() + [self.multinic]

    def start_delay(self):
        return 0

Jonas Kaufmann's avatar
Jonas Kaufmann committed
727

Jonas Kaufmann's avatar
Jonas Kaufmann committed
728
class I40eMultiNIC(Simulator):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
729

730
731
    def __init__(self):
        super().__init__()
732
        self.subnics = []
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749

    def create_subnic(self):
        sn = MultiSubNIC(self)
        self.subnics.append(sn)
        return sn

    def full_name(self):
        return 'multinic.' + self.name

    def run_cmd(self, env):
        args = ''
        first = True
        for sn in self.subnics:
            if not first:
                args += ' -- '
            first = False
            args += sn.basic_args(env)
750
        return f'{env.repodir}/sims/nic/i40e_bm/i40e_bm {args}'
751
752
753
754
755
756
757
758
759
760
761
762

    def sockets_cleanup(self, env):
        ss = []
        for sn in self.subnics:
            ss += sn.sockets_cleanup(env)
        return ss

    def sockets_wait(self, env):
        ss = []
        for sn in self.subnics:
            ss += sn.sockets_wait(env)
        return ss
763
764
765


class WireNet(NetSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
766

767
    def run_cmd(self, env):
Jialin Li's avatar
Jialin Li committed
768
        connects = self.connect_sockets(env)
769
        assert len(connects) == 2
770
771
        cmd = (
            f'{env.repodir}/sims/net/wire/net_wire {connects[0][1]}'
772
            f' {connects[1][1]} {self.sync_mode} {self.sync_period}'
773
774
            f' {self.eth_latency}'
        )
775
776
777
        if len(env.pcap_file) > 0:
            cmd += ' ' + env.pcap_file
        return cmd
778

Jonas Kaufmann's avatar
Jonas Kaufmann committed
779

780
class SwitchNet(NetSim):
781
782
783
784

    def __init__(self):
        super().__init__()
        self.sync = True
785
        """Whether to synchronize with attached simulators."""
786

787
    def run_cmd(self, env):
788
        cmd = env.repodir + '/sims/net/switch/net_switch'
789
        cmd += f' -S {self.sync_period} -E {self.eth_latency}'
790
791
792
793

        if not self.sync:
            cmd += ' -u'

794
795
        if len(env.pcap_file) > 0:
            cmd += ' -p ' + env.pcap_file
Jonas Kaufmann's avatar
Jonas Kaufmann committed
796
        for (_, n) in self.connect_sockets(env):
797
            cmd += ' -s ' + n
Jonas Kaufmann's avatar
Jonas Kaufmann committed
798
        for (_, n) in self.listen_sockets(env):
799
            cmd += ' -h ' + n
800
        return cmd
801

802
803
    def sockets_cleanup(self, env):
        # cleanup here will just have listening eth sockets, switch also creates
Hejing Li's avatar
Hejing Li committed
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
        # shm regions for each with a "-shm" suffix
        cleanup = []
        for s in super().sockets_cleanup(env):
            cleanup.append(s)
            cleanup.append(s + '-shm')
        return cleanup


class MemSwitchNet(NetSim):

    def __init__(self):
        super().__init__()
        self.sync = True
        """ AS_ID,VADDR_START,VADDR_END,MEMNODE_MAC,PHYS_START """
        self.mem_map = []

    def run_cmd(self, env):
        cmd = env.repodir + '/sims/mem/memswitch/memswitch'
        cmd += f' -S {self.sync_period} -E {self.eth_latency}'

        if not self.sync:
            cmd += ' -u'

        if len(env.pcap_file) > 0:
            cmd += ' -p ' + env.pcap_file
        for (_, n) in self.connect_sockets(env):
            cmd += ' -s ' + n
        for (_, n) in self.listen_sockets(env):
            cmd += ' -h ' + n
        for m in self.mem_map:
            cmd += ' -m ' + f' {m[0]},{m[1]},{m[2]},'
            cmd += (''.join(reversed(m[3].split(':'))))
            cmd += f',{m[4]}'
        return cmd

    def sockets_cleanup(self, env):
        # cleanup here will just have listening eth sockets, switch also creates
841
842
843
844
845
846
847
        # shm regions for each with a "-shm" suffix
        cleanup = []
        for s in super().sockets_cleanup(env):
            cleanup.append(s)
            cleanup.append(s + '-shm')
        return cleanup

Jonas Kaufmann's avatar
Jonas Kaufmann committed
848

849
class TofinoNet(NetSim):
850
851
852
853
854

    def __init__(self):
        super().__init__()
        self.tofino_log_path = '/tmp/model.ldjson'
        self.sync = True
Jialin Li's avatar
Jialin Li committed
855

856
    def run_cmd(self, env):
857
858
859
860
861
        cmd = f'{env.repodir}/sims/net/tofino/tofino'
        cmd += (
            f' -S {self.sync_period} -E {self.eth_latency}'
            f' -t {self.tofino_log_path}'
        )
862
863
        if not self.sync:
            cmd += ' -u'
Jonas Kaufmann's avatar
Jonas Kaufmann committed
864
        for (_, n) in self.connect_sockets(env):
865
            cmd += ' -s ' + n
866
        return cmd
867

Jonas Kaufmann's avatar
Jonas Kaufmann committed
868

869
class NS3DumbbellNet(NetSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
870

871
872
    def run_cmd(self, env):
        ports = ''
Jonas Kaufmann's avatar
Jonas Kaufmann committed
873
        for (n, s) in self.connect_sockets(env):
874
            if 'server' in n.name:
Jonas Kaufmann's avatar
Jonas Kaufmann committed
875
                ports += f'--CosimPortLeft={s} '
876
            else:
Jonas Kaufmann's avatar
Jonas Kaufmann committed
877
                ports += f'--CosimPortRight={s} '
878

879
880
881
882
        cmd = (
            f'{env.repodir}/sims/external/ns-3'
            f'/cosim-run.sh cosim cosim-dumbbell-example {ports} {self.opt}'
        )
883
884
885
886
        print(cmd)

        return cmd

Jonas Kaufmann's avatar
Jonas Kaufmann committed
887

888
class NS3BridgeNet(NetSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
889

890
891
    def run_cmd(self, env):
        ports = ''
Jonas Kaufmann's avatar
Jonas Kaufmann committed
892
        for (_, n) in self.connect_sockets(env):
893
            ports += '--CosimPort=' + n + ' '
894

895
896
897
898
        cmd = (
            f'{env.repodir}/sims/external/ns-3'
            f'/cosim-run.sh cosim cosim-bridge-example {ports} {self.opt}'
        )
899
900
901
        print(cmd)

        return cmd
902

Jonas Kaufmann's avatar
Jonas Kaufmann committed
903

904
class NS3SequencerNet(NetSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
905

906
907
    def run_cmd(self, env):
        ports = ''
Jonas Kaufmann's avatar
Jonas Kaufmann committed
908
        for (n, s) in self.connect_sockets(env):
909
            if 'client' in n.name:
910
                ports += '--ClientPort=' + s + ' '
911
            elif 'replica' in n.name:
912
                ports += '--ServerPort=' + s + ' '
913
            elif 'sequencer' in n.name:
914
                ports += '--ServerPort=' + s + ' '
915
            else:
916
                raise KeyError('Wrong NIC type')
917
918
919
920
921
        cmd = (
            f'{env.repodir}/sims/external/ns-3'
            f'/cosim-run.sh sequencer sequencer-single-switch-example'
            f' {ports} {self.opt}'
        )
922
923
924
        return cmd


925
class FEMUDev(PCIDevSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
926

927
    def run_cmd(self, env):
928
929
930
931
        cmd = (
            f'{env.repodir}/sims/external/femu/femu-simbricks'
            f' {env.dev_pci_path(self)} {env.dev_shm_path(self)}'
        )
932
        return cmd
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987


class BasicMemDev(MemDevSim):

    def run_cmd(self, env):
        cmd = (
            f'{env.repodir}/sims/mem/basicmem/basicmem'
            f' {self.size} {self.addr} {self.as_id}'
            f' {env.dev_mem_path(self)} {env.dev_shm_path(self)}'
            f' {self.sync_mode} {self.start_tick} {self.sync_period}'
            f' {self.mem_latency}'
        )
        return cmd


class MemNIC(MemDevSim):

    def run_cmd(self, env):
        cmd = (
            f'{env.repodir}/sims/mem/memnic/memnic'
            f' {env.dev_mem_path(self)} {env.nic_eth_path(self)}'
            f' {env.dev_shm_path(self)}'
        )

        if self.mac is not None:
            cmd += ' ' + (''.join(reversed(self.mac.split(':'))))

        cmd += f' {self.sync_mode} {self.start_tick} {self.sync_period}'
        cmd += f' {self.mem_latency} {self.eth_latency}'

        return cmd

    def sockets_cleanup(self, env):
        return super().sockets_cleanup(env) + [env.nic_eth_path(self)]

    def sockets_wait(self, env):
        return super().sockets_wait(env) + [env.nic_eth_path(self)]


class NetMem(NetMemSim):

    def run_cmd(self, env):
        cmd = (
            f'{env.repodir}/sims/mem/netmem/netmem'
            f' {self.size} {self.addr} {self.as_id}'
            f' {env.nic_eth_path(self)}'
            f' {env.dev_shm_path(self)}'
        )
        if self.mac is not None:
            cmd += ' ' + (''.join(reversed(self.mac.split(':'))))

        cmd += f' {self.sync_mode} {self.start_tick} {self.sync_period}'
        cmd += f' {self.eth_latency}'

        return cmd