simulators.py 36.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
import sys
28
29
import typing as tp

30
31
from simbricks.orchestration.experiment.experiment_environment import ExpEnv
from simbricks.orchestration.nodeconfig import NodeConfig
32
33
from simbricks.orchestration.e2e_topologies import E2ETopology
from simbricks.orchestration import e2e_components as e2e
34

35

36
class Simulator(object):
37
38
    """Base class for all simulators."""

39
40
    def __init__(self) -> None:
        self.extra_deps: tp.List[Simulator] = []
Jonas Kaufmann's avatar
Jonas Kaufmann committed
41
        self.name = ''
42

43
    def resreq_cores(self) -> int:
44
45
46
47
48
        """
        Number of cores this simulator requires during execution.

        This is used for scheduling multiple runs and experiments.
        """
49
50
        return 1

51
    def resreq_mem(self) -> int:
52
53
54
55
56
        """
        Number of memory in MB this simulator requires during execution.

        This is used for scheduling multiple runs and experiments.
        """
57
58
        return 64

59
    def full_name(self) -> str:
Jonas Kaufmann's avatar
Jonas Kaufmann committed
60
61
62
        """Full name of the simulator."""
        return ''

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

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

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

    # Sockets to be cleaned up
78
    # pylint: disable=unused-argument
79
    def sockets_cleanup(self, env: ExpEnv) -> tp.List[str]:
80
81
82
        return []

    # sockets to wait for indicating the simulator is ready
83
    # pylint: disable=unused-argument
84
    def sockets_wait(self, env: ExpEnv) -> tp.List[str]:
85
86
        return []

87
    def start_delay(self) -> int:
88
89
        return 5

90
    def wait_terminate(self) -> bool:
91
92
        return False

93

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

97
    def __init__(self) -> None:
98
99
100
        super().__init__()

        self.sync_mode = 0
101
102
        """Synchronization mode. 0 is running unsynchronized, 1 synchronized.
        Depending on the concrete simulator, there may be additional modes."""
103
        self.start_tick = 0
104
105
106
107
108
        """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."""
109
        self.sync_period = 500
110
111
        """Period in nanoseconds of sending synchronization messages from this
        device to connected components."""
112
        self.pci_latency = 500
113
114
        """Latency in nanoseconds for sending messages to components connected
        via PCI."""
115

116
    def full_name(self) -> str:
117
118
        return 'dev.' + self.name

119
    def is_nic(self) -> bool:
120
121
        return False

122
    def sockets_cleanup(self, env: ExpEnv) -> tp.List[str]:
123
124
        return [env.dev_pci_path(self), env.dev_shm_path(self)]

125
    def sockets_wait(self, env: ExpEnv) -> tp.List[str]:
126
127
128
129
        return [env.dev_pci_path(self)]


class NICSim(PCIDevSim):
130
131
    """Base class for NIC simulators."""

132
    def __init__(self) -> None:
133
134
135
136
137
        super().__init__()

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

141
    def set_network(self, net: NetSim) -> None:
142
        """Connect this NIC to a network simulator."""
143
144
145
        self.network = net
        net.nics.append(self)

146
    def basic_args(self, env: ExpEnv, extra: tp.Optional[str] = None) -> str:
147
148
        cmd = (
            f'{env.dev_pci_path(self)} {env.nic_eth_path(self)}'
149
150
            f' {env.dev_shm_path(self)} {self.sync_mode} {self.start_tick}'
            f' {self.sync_period} {self.pci_latency} {self.eth_latency}'
151
        )
152
153
        if self.mac is not None:
            cmd += ' ' + (''.join(reversed(self.mac.split(':'))))
154
155
156
157

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

159
160
161
    def basic_run_cmd(
        self, env: ExpEnv, name: str, extra: tp.Optional[str] = None
    ) -> str:
162
        cmd = f'{env.repodir}/sims/nic/{name} {self.basic_args(env, extra)}'
163
164
        return cmd

165
    def full_name(self) -> str:
166
        return 'nic.' + self.name
167

168
    def is_nic(self) -> bool:
169
170
        return True

171
    def sockets_cleanup(self, env: ExpEnv) -> tp.List[str]:
172
        return super().sockets_cleanup(env) + [env.nic_eth_path(self)]
173

174
    def sockets_wait(self, env: ExpEnv) -> tp.List[str]:
175
        return super().sockets_wait(env) + [env.nic_eth_path(self)]
176

Jonas Kaufmann's avatar
Jonas Kaufmann committed
177

178
class NetSim(Simulator):
179
    """Base class for network simulators."""
180

181
    def __init__(self) -> None:
182
183
184
185
        super().__init__()

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

199
    def full_name(self) -> str:
200
        return 'net.' + self.name
201

202
    def connect_network(self, net: NetSim) -> None:
203
        """Connect this network to the listening peer `net`"""
204
205
206
        net.net_listen.append(self)
        self.net_connect.append(net)

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

217
    def listen_sockets(self, env: ExpEnv) -> tp.List[tp.Tuple[NetSim, str]]:
218
219
220
221
222
        listens = []
        for net in self.net_listen:
            listens.append((net, env.n2n_eth_path(self, net)))
        return listens

223
    def dependencies(self) -> tp.List[Simulator]:
224
        return self.nics + self.net_connect + self.hosts_direct
225

226
    def sockets_cleanup(self, env: ExpEnv) -> tp.List[str]:
Jonas Kaufmann's avatar
Jonas Kaufmann committed
227
        return [s for (_, s) in self.listen_sockets(env)]
228

229
    def sockets_wait(self, env: ExpEnv) -> tp.List[str]:
Jonas Kaufmann's avatar
Jonas Kaufmann committed
230
        return [s for (_, s) in self.listen_sockets(env)]
231

232

233
234
235
236
# FIXME: Class hierarchy is broken here as an ugly hack
class MemDevSim(NICSim):
    """Base class for memory device simulators."""

237
    def __init__(self) -> None:
238
239
240
241
242
243
244
        super().__init__()

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

245
    def full_name(self) -> str:
246
247
        return 'mem.' + self.name

248
    def sockets_cleanup(self, env: ExpEnv) -> tp.List[str]:
249
250
        return [env.dev_mem_path(self), env.dev_shm_path(self)]

251
    def sockets_wait(self, env: ExpEnv) -> tp.List[str]:
252
253
254
255
256
257
        return [env.dev_mem_path(self)]


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

258
    def __init__(self) -> None:
259
260
261
262
263
264
        super().__init__()

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

265
    def full_name(self) -> str:
266
267
        return 'netmem.' + self.name

268
    def sockets_cleanup(self, env: ExpEnv) -> tp.List[str]:
269
270
        return [env.nic_eth_path(self), env.dev_shm_path(self)]

271
    def sockets_wait(self, env: ExpEnv) -> tp.List[str]:
272
273
274
        return [env.nic_eth_path(self)]


275
class HostSim(Simulator):
276
    """Base class for host simulators."""
277

278
    def __init__(self, node_config: NodeConfig) -> None:
279
        super().__init__()
280
        self.node_config = node_config
281
        """System configuration for this simulated host. """
282
283
        self.wait = False
        """
284
285
286
        `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.
287
288
        """
        self.sleep = 0
289
        self.cpu_freq = '4GHz'
290
291

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

304
305
306
307
308
309
310
311
        self.sync_drift: tp.Optional[int] = None
        """Conversion factor from SimBricks' picosecond timestamps to the host's
        nanosecond clock. None or 1000 is 1:1, while other values result in
        simulated clock drift for a host."""
        self.sync_offset: tp.Optional[int] = None
        """Clock offset for this host in picoseconds. None or 0 does not add an
        offset."""

312
        self.pcidevs: tp.List[PCIDevSim] = []
313
        self.net_directs: tp.List[NetSim] = []
314
        self.memdevs: tp.List[MemDevSim] = []
315

316
    @property
317
318
319
320
321
322
    def nics(self) -> tp.List[NICSim]:
        return [
            tp.cast(NICSim, pcidev)
            for pcidev in self.pcidevs
            if pcidev.is_nic()
        ]
323

324
    def full_name(self) -> str:
325
326
        return 'host.' + self.name

327
    def add_nic(self, dev: NICSim) -> None:
328
        """Add a NIC to this host."""
329
330
        self.add_pcidev(dev)

331
    def add_pcidev(self, dev: PCIDevSim) -> None:
332
        """Add a PCIe device to this host."""
333
334
335
        dev.name = self.name + '.' + dev.name
        self.pcidevs.append(dev)

336
    def add_memdev(self, dev: MemDevSim) -> None:
337
338
339
        dev.name = self.name + '.' + dev.name
        self.memdevs.append(dev)

340
    def add_netdirect(self, net: NetSim) -> None:
341
        """Add a direct connection to a network to this host."""
342
343
344
        net.hosts_direct.append(self)
        self.net_directs.append(net)

345
    def dependencies(self) -> tp.List[PCIDevSim]:
346
347
348
349
350
        deps = []
        for dev in self.pcidevs:
            deps.append(dev)
            if isinstance(dev, NICSim):
                deps.append(dev.network)
351
352
        for dev in self.memdevs:
            deps.append(dev)
353
354
        return deps

355
    def wait_terminate(self) -> bool:
356
357
358
        return self.wait


359
class QemuHost(HostSim):
360
361
    """Qemu host simulator."""

362
    def __init__(self, node_config: NodeConfig) -> None:
363
        super().__init__(node_config)
364
365

        self.sync = False
366
        """"Whether to synchronize with attached simulators."""
367

368
    def resreq_cores(self) -> int:
369
370
371
372
        if self.sync:
            return 1
        else:
            return self.node_config.cores + 1
373

374
    def resreq_mem(self) -> int:
Hejing Li's avatar
Hejing Li committed
375
        return 8192
376

377
    def prep_cmds(self, env: ExpEnv) -> tp.List[str]:
Jonas Kaufmann's avatar
Jonas Kaufmann committed
378
379
        return [
            f'{env.qemu_img_path} create -f qcow2 -o '
380
            f'backing_file="{env.hd_path(self.node_config.disk_image)}" '
Jonas Kaufmann's avatar
Jonas Kaufmann committed
381
382
            f'{env.hdcopy_path(self)}'
        ]
383

384
    def run_cmd(self, env: ExpEnv) -> str:
385
        accel = ',accel=kvm:tcg' if not self.sync else ''
386
387
388
389
390
        if self.node_config.kcmd_append:
            kcmd_append = ' ' + self.node_config.kcmd_append
        else:
            kcmd_append = ''

Jonas Kaufmann's avatar
Jonas Kaufmann committed
391
392
        cmd = (
            f'{env.qemu_path} -machine q35{accel} -serial mon:stdio '
393
            '-cpu Skylake-Server -display none -nic none '
394
395
396
            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
397
            'driver=raw '
398
            '-append "earlyprintk=ttyS0 console=ttyS0 root=/dev/sda1 '
399
            f'init=/home/ubuntu/guestinit.sh rw{kcmd_append}" '
Jonas Kaufmann's avatar
Jonas Kaufmann committed
400
401
            f'-m {self.node_config.memory} -smp {self.node_config.cores} '
        )
402
403

        if self.sync:
404
405
406
407
408
409
            unit = self.cpu_freq[-3:]
            if unit.lower() == 'ghz':
                base = 0
            elif unit.lower() == 'mhz':
                base = 3
            else:
410
                raise ValueError('cpu frequency specified in unsupported unit')
411
412
413
            num = float(self.cpu_freq[:-3])
            shift = base - int(math.ceil(math.log(num, 2)))

414
            cmd += f' -icount shift={shift},sleep=off '
415

416
        for dev in self.pcidevs:
417
            cmd += f'-device simbricks-pci,socket={env.dev_pci_path(dev)}'
418
419
420
421
            if self.sync:
                cmd += ',sync=on'
                cmd += f',pci-latency={self.pci_latency}'
                cmd += f',sync-period={self.sync_period}'
422
423
424
425
                if self.sync_drift is not None:
                    cmd += f',sync-drift={self.sync_drift}'
                if self.sync_offset is not None:
                    cmd += f',sync-offset={self.sync_offset}'
426
427
428
429
            else:
                cmd += ',sync=off'
            cmd += ' '

430
        # qemu does not currently support net direct ports
431
        assert len(self.net_directs) == 0
432
433
        # qemu does not currently support mem device ports
        assert len(self.memdevs) == 0
434
435
        return cmd

Jonas Kaufmann's avatar
Jonas Kaufmann committed
436

437
class Gem5Host(HostSim):
438
    """Gem5 host simulator."""
439

440
    def __init__(self, node_config: NodeConfig) -> None:
441
442
        node_config.sim = 'gem5'
        super().__init__(node_config)
443
444
445
        self.cpu_type_cp = 'X86KvmCPU'
        self.cpu_type = 'TimingSimpleCPU'
        self.sys_clock = '1GHz'
446
447
        self.extra_main_args = []
        self.extra_config_args = []
448
        self.variant = 'fast'
449
450
451
452
453
454
455
456
        self.modify_checkpoint_tick = True
        """Whether to modify the event queue tick before restoring a checkpoint.
        When this is enabled, the restored checkpoint will start at event queue
        tick 0. This is a performance optimization since now, connected
        simulators don't have to simulate and synchronize until the restored
        tick before the actual workload can be executed. Disable this if you
        need to retain the differences in virtual time between multiple gem5
        instances."""
457

458
    def resreq_cores(self) -> int:
459
460
        return 1

461
    def resreq_mem(self) -> int:
462
463
        return 4096

464
    def prep_cmds(self, env: ExpEnv) -> tp.List[str]:
465
466
467
468
469
470
471
        cmds = [f'mkdir -p {env.gem5_cpdir(self)}']
        if env.restore_cp and self.modify_checkpoint_tick:
            cmds.append(
                f'python3 {env.utilsdir}/modify_gem5_cp_tick.py --tick 0 '
                f'--cpdir {env.gem5_cpdir(self)}'
            )
        return cmds
472

473
    def run_cmd(self, env: ExpEnv) -> str:
474
475
476
477
        cpu_type = self.cpu_type
        if env.create_cp:
            cpu_type = self.cpu_type_cp

478
        cmd = f'{env.gem5_path(self.variant)} --outdir={env.gem5_outdir(self)} '
479
        cmd += ' '.join(self.extra_main_args)
Jonas Kaufmann's avatar
Jonas Kaufmann committed
480
        cmd += (
481
482
483
            f' {env.gem5_py_path} --caches --l2cache '
            '--l1d_size=32kB --l1i_size=32kB --l2_size=32MB '
            '--l1d_assoc=8 --l1i_assoc=8 --l2_assoc=16 '
484
485
            f'--cacheline_size=64 --cpu-clock={self.cpu_freq}'
            f' --sys-clock={self.sys_clock} '
486
487
            f'--checkpoint-dir={env.gem5_cpdir(self)} '
            f'--kernel={env.gem5_kernel_path} '
488
            f'--disk-image={env.hd_raw_path(self.node_config.disk_image)} '
489
            f'--disk-image={env.cfgtar_path(self)} '
490
            f'--cpu-type={cpu_type} --mem-size={self.node_config.memory}MB '
491
            f'--num-cpus={self.node_config.cores} '
492
            '--mem-type=DDR4_2400_16x4 '
Jonas Kaufmann's avatar
Jonas Kaufmann committed
493
        )
494

495
496
497
        if self.node_config.kcmd_append:
            cmd += f'--command-line-append="{self.node_config.kcmd_append}" '

498
499
        if env.create_cp:
            cmd += '--max-checkpoints=1 '
500

501
        if env.restore_cp:
502
            cmd += '-r 1 '
503

504
        for dev in self.pcidevs:
Jonas Kaufmann's avatar
Jonas Kaufmann committed
505
506
507
508
509
            cmd += (
                f'--simbricks-pci=connect:{env.dev_pci_path(dev)}'
                f':latency={self.pci_latency}ns'
                f':sync_interval={self.sync_period}ns'
            )
510
            if cpu_type == 'TimingSimpleCPU':
511
                cmd += ':sync'
Jonas Kaufmann's avatar
Jonas Kaufmann committed
512
            cmd += ' '
513

514
515
516
517
518
519
520
521
522
523
524
        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 += ' '

525
        for net in self.net_directs:
Jonas Kaufmann's avatar
Jonas Kaufmann committed
526
527
528
529
530
531
532
            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'
            )
533
534
            if cpu_type == 'TimingSimpleCPU':
                cmd += ':sync'
Jonas Kaufmann's avatar
Jonas Kaufmann committed
535
            cmd += ' '
536
537

        cmd += ' '.join(self.extra_config_args)
538
539
540
        return cmd


541
542
543
class SimicsHost(HostSim):
    """Simics host simulator."""

544
    def __init__(self, node_config: NodeConfig) -> None:
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
        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."""

565
    def resreq_cores(self) -> int:
566
567
        return 2

568
    def resreq_mem(self) -> int:
569
570
        return self.node_config.memory

571
    def run_cmd(self, env: ExpEnv) -> str:
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
        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} '
596
597
                f'memory_megs = {self.node_config.memory} '
                'create_network = FALSE\' '
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
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
            )

        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\' '

674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
        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

691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
        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'


706
class CorundumVerilatorNIC(NICSim):
707

708
    def __init__(self) -> None:
709
710
        super().__init__()
        self.clock_freq = 250  # MHz
711

712
    def resreq_mem(self) -> int:
713
714
715
        # this is a guess
        return 512

716
    def run_cmd(self, env: ExpEnv) -> str:
Jonas Kaufmann's avatar
Jonas Kaufmann committed
717
718
719
720
        return self.basic_run_cmd(
            env, '/corundum/corundum_verilator', str(self.clock_freq)
        )

721
722

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

724
    def run_cmd(self, env: ExpEnv) -> str:
Hejing Li's avatar
Hejing Li committed
725
        return self.basic_run_cmd(env, '/corundum_bm/corundum_bm')
726

Jonas Kaufmann's avatar
Jonas Kaufmann committed
727

728
class I40eNIC(NICSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
729

730
    def run_cmd(self, env: ExpEnv) -> str:
Hejing Li's avatar
Hejing Li committed
731
        return self.basic_run_cmd(env, '/i40e_bm/i40e_bm')
732

Jonas Kaufmann's avatar
Jonas Kaufmann committed
733

734
class E1000NIC(NICSim):
735

736
    def __init__(self) -> None:
737
738
        super().__init__()
        self.debug = False
Jonas Kaufmann's avatar
Jonas Kaufmann committed
739

740
    def run_cmd(self, env: ExpEnv) -> str:
741
742
743
744
        cmd = self.basic_run_cmd(env, '/e1000_gem5/e1000_gem5')
        if self.debug:
            cmd = 'env E1000_DEBUG=1 ' + cmd
        return cmd
745

Jonas Kaufmann's avatar
Jonas Kaufmann committed
746

747
748
class MultiSubNIC(NICSim):

749
    def __init__(self, mn: Simulator) -> None:
750
        super().__init__()
751
        self.multinic = mn
752

753
    def full_name(self) -> str:
754
755
        return self.multinic.full_name() + '.' + self.name

756
    def dependencies(self) -> tp.List[Simulator]:
757
758
        return super().dependencies() + [self.multinic]

759
    def start_delay(self) -> int:
760
761
        return 0

Jonas Kaufmann's avatar
Jonas Kaufmann committed
762

Jonas Kaufmann's avatar
Jonas Kaufmann committed
763
class I40eMultiNIC(Simulator):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
764

765
    def __init__(self) -> None:
766
        super().__init__()
767
        self.subnics: tp.List[NICSim] = []
768

769
    def create_subnic(self) -> MultiSubNIC:
770
771
772
773
        sn = MultiSubNIC(self)
        self.subnics.append(sn)
        return sn

774
    def full_name(self) -> str:
775
776
        return 'multinic.' + self.name

777
    def run_cmd(self, env: ExpEnv) -> str:
778
779
780
781
782
783
784
        args = ''
        first = True
        for sn in self.subnics:
            if not first:
                args += ' -- '
            first = False
            args += sn.basic_args(env)
785
        return f'{env.repodir}/sims/nic/i40e_bm/i40e_bm {args}'
786

787
    def sockets_cleanup(self, env: ExpEnv) -> tp.List[str]:
788
789
790
791
792
        ss = []
        for sn in self.subnics:
            ss += sn.sockets_cleanup(env)
        return ss

793
    def sockets_wait(self, env: ExpEnv) -> tp.List[str]:
794
795
796
797
        ss = []
        for sn in self.subnics:
            ss += sn.sockets_wait(env)
        return ss
798
799
800


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

802
    def run_cmd(self, env: ExpEnv) -> str:
Jialin Li's avatar
Jialin Li committed
803
        connects = self.connect_sockets(env)
804
        assert len(connects) == 2
805
806
        cmd = (
            f'{env.repodir}/sims/net/wire/net_wire {connects[0][1]}'
807
            f' {connects[1][1]} {self.sync_mode} {self.sync_period}'
808
809
            f' {self.eth_latency}'
        )
810
811
812
        if len(env.pcap_file) > 0:
            cmd += ' ' + env.pcap_file
        return cmd
813

Jonas Kaufmann's avatar
Jonas Kaufmann committed
814

815
class SwitchNet(NetSim):
816

817
    def __init__(self) -> None:
818
819
        super().__init__()
        self.sync = True
820
        """Whether to synchronize with attached simulators."""
821

822
    def run_cmd(self, env: ExpEnv) -> str:
823
        cmd = env.repodir + '/sims/net/switch/net_switch'
824
        cmd += f' -S {self.sync_period} -E {self.eth_latency}'
825
826
827
828

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

829
830
        if len(env.pcap_file) > 0:
            cmd += ' -p ' + env.pcap_file
Jonas Kaufmann's avatar
Jonas Kaufmann committed
831
        for (_, n) in self.connect_sockets(env):
832
            cmd += ' -s ' + n
Jonas Kaufmann's avatar
Jonas Kaufmann committed
833
        for (_, n) in self.listen_sockets(env):
834
            cmd += ' -h ' + n
835
        return cmd
836

837
    def sockets_cleanup(self, env: ExpEnv) -> tp.List[str]:
838
        # cleanup here will just have listening eth sockets, switch also creates
Hejing Li's avatar
Hejing Li committed
839
840
841
842
843
844
845
846
847
848
        # 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):

849
    def __init__(self) -> None:
Hejing Li's avatar
Hejing Li committed
850
851
852
853
854
        super().__init__()
        self.sync = True
        """ AS_ID,VADDR_START,VADDR_END,MEMNODE_MAC,PHYS_START """
        self.mem_map = []

855
    def run_cmd(self, env: ExpEnv) -> str:
Hejing Li's avatar
Hejing Li committed
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
        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

874
    def sockets_cleanup(self, env: ExpEnv) -> tp.List[str]:
Hejing Li's avatar
Hejing Li committed
875
        # cleanup here will just have listening eth sockets, switch also creates
876
877
878
879
880
881
882
        # 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
883

884
class TofinoNet(NetSim):
885

886
    def __init__(self) -> None:
887
888
889
        super().__init__()
        self.tofino_log_path = '/tmp/model.ldjson'
        self.sync = True
Jialin Li's avatar
Jialin Li committed
890

891
    def run_cmd(self, env: ExpEnv) -> str:
892
893
894
895
896
        cmd = f'{env.repodir}/sims/net/tofino/tofino'
        cmd += (
            f' -S {self.sync_period} -E {self.eth_latency}'
            f' -t {self.tofino_log_path}'
        )
897
898
        if not self.sync:
            cmd += ' -u'
Jonas Kaufmann's avatar
Jonas Kaufmann committed
899
        for (_, n) in self.connect_sockets(env):
900
            cmd += ' -s ' + n
901
        return cmd
902

Jonas Kaufmann's avatar
Jonas Kaufmann committed
903

904
905
906
907
class NS3E2ENet(NetSim):

    def __init__(self) -> None:
        super().__init__()
908
909
910
911
        self.first_run = True
        self.e2e_components: tp.List[tp.Union[e2e.E2ETopologyNode,
                                              e2e.E2ETopologyChannel]] = []
        self.e2e_topologies: tp.List[E2ETopology] = []
912
        self.use_file = True
913
914
915
916
917
918
919
920
921
922
923

    def add_component(
        self,
        component: tp.Union[e2e.E2ETopologyNode,
                            e2e.E2ETopologyChannel,
                            E2ETopology]
    ):
        if isinstance(component, E2ETopology):
            self.e2e_topologies.append(component)
        else:
            self.e2e_components.append(component)
924
925

    def resolve_socket_paths(
926
927
928
929
        self,
        env: ExpEnv,
        e2e_sim: tp.Union[e2e.E2ESimbricksNetwork, e2e.E2ESimbricksHost],
        listen: bool = False
930
    ) -> None:
931
932
        if e2e_sim.simbricks_component is None:
            print('E2E Simbricks adapter does not contain a simulator')
933
            sys.exit(1)
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
        if e2e_sim.adapter_type == e2e.SimbricksAdapterType.NIC:
            e2e_sim.unix_socket = env.nic_eth_path(e2e_sim.simbricks_component)
        elif e2e_sim.adapter_type == e2e.SimbricksAdapterType.NETWORK:
            if listen:
                e2e_sim.unix_socket = env.n2n_eth_path(
                    self, e2e_sim.simbricks_component
                )
            else:
                e2e_sim.unix_socket = env.n2n_eth_path(
                    e2e_sim.simbricks_component, self
                )
        elif e2e_sim.adapter_type == e2e.SimbricksAdapterType.HOST:
            e2e_sim.unix_socket = env.net2host_eth_path(
                self, e2e_sim.simbricks_component
            )
949
950

    def run_cmd(self, env):
951
952
953
954
955
956
957
        if self.first_run:
            for topo in self.e2e_topologies:
                topo.add_to_network(self)

        for component in self.e2e_components:
            if self.first_run:
                component.resolve_paths()
958

959
960
            for c in component.components:
                if isinstance(c, e2e.E2ESimbricksHost):
961
                    self.resolve_socket_paths(env, c)
962
963
964
965
966
967
                elif isinstance(c, e2e.E2ESimbricksNetworkNetIf):
                    self.resolve_socket_paths(env, c)
                    if self.first_run:
                        self.connect_network(c.simbricks_component)
                elif isinstance(c, e2e.E2ESimbricksNetworkNicIf):
                    self.resolve_socket_paths(env, c, True)
968

969
970
        self.first_run = False

971
972
973
974
        params: tp.List[str] = []
        for component in self.e2e_components:
            params.append(component.ns3_config())

975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
        params_str = f'{" ".join(params)} {self.opt}'

        if self.use_file:
            file_path = env.ns3_e2e_params_file(self)
            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(params_str)
            cmd = (
                f'{env.repodir}/sims/external/ns-3'
                f'/simbricks-run.sh e2e-cc-example --ConfigFile={file_path}'
            )
        else:
            cmd = (
                f'{env.repodir}/sims/external/ns-3'
                f'/simbricks-run.sh e2e-cc-example {params_str}'
            )
990
991
992
993
994
        print(cmd)

        return cmd


995
class NS3DumbbellNet(NetSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
996

997
    def run_cmd(self, env: ExpEnv) -> str:
998
        ports = ''
Jonas Kaufmann's avatar
Jonas Kaufmann committed
999
        for (n, s) in self.connect_sockets(env):
1000
            if 'server' in n.name:
Marvin Meiers's avatar
Marvin Meiers committed
1001
                ports += f'--SimbricksPortLeft={s} '
1002
            else:
Marvin Meiers's avatar
Marvin Meiers committed
1003
                ports += f'--SimbricksPortRight={s} '
1004

1005
1006
        cmd = (
            f'{env.repodir}/sims/external/ns-3'
Marvin Meiers's avatar
Marvin Meiers committed
1007
            f'/simbricks-run.sh simbricks-dumbbell-example {ports} {self.opt}'
1008
        )
1009
1010
1011
1012
        print(cmd)

        return cmd

Jonas Kaufmann's avatar
Jonas Kaufmann committed
1013

1014
class NS3BridgeNet(NetSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
1015

1016
    def run_cmd(self, env: ExpEnv) -> str:
1017
        ports = ''
Jonas Kaufmann's avatar
Jonas Kaufmann committed
1018
        for (_, n) in self.connect_sockets(env):
Marvin Meiers's avatar
Marvin Meiers committed
1019
            ports += '--SimbricksPort=' + n + ' '
1020

1021
1022
        cmd = (
            f'{env.repodir}/sims/external/ns-3'
Marvin Meiers's avatar
Marvin Meiers committed
1023
            f'/simbricks-run.sh simbricks-bridge-example {ports} {self.opt}'
1024
        )
1025
1026
1027
        print(cmd)

        return cmd
1028

Jonas Kaufmann's avatar
Jonas Kaufmann committed
1029

1030
class NS3SequencerNet(NetSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
1031

1032
    def run_cmd(self, env: ExpEnv) -> str:
1033
        ports = ''
Jonas Kaufmann's avatar
Jonas Kaufmann committed
1034
        for (n, s) in self.connect_sockets(env):
1035
            if 'client' in n.name:
1036
                ports += '--ClientPort=' + s + ' '
1037
            elif 'replica' in n.name:
1038
                ports += '--ServerPort=' + s + ' '
1039
            elif 'sequencer' in n.name:
1040
                ports += '--ServerPort=' + s + ' '
1041
            else:
1042
                raise KeyError('Wrong NIC type')
1043
1044
        cmd = (
            f'{env.repodir}/sims/external/ns-3'
Marvin Meiers's avatar
Marvin Meiers committed
1045
            f'/simbricks-run.sh sequencer-single-switch-example'
1046
1047
            f' {ports} {self.opt}'
        )
1048
1049
1050
        return cmd


1051
class FEMUDev(PCIDevSim):
Jonas Kaufmann's avatar
Jonas Kaufmann committed
1052

1053
    def run_cmd(self, env: ExpEnv) -> str:
1054
1055
1056
1057
        cmd = (
            f'{env.repodir}/sims/external/femu/femu-simbricks'
            f' {env.dev_pci_path(self)} {env.dev_shm_path(self)}'
        )
1058
        return cmd
1059
1060
1061
1062


class BasicMemDev(MemDevSim):

1063
    def run_cmd(self, env: ExpEnv) -> str:
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
        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):

1076
    def run_cmd(self, env: ExpEnv) -> str:
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
        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

1091
    def sockets_cleanup(self, env: ExpEnv) -> tp.List[str]:
1092
1093
        return super().sockets_cleanup(env) + [env.nic_eth_path(self)]

1094
    def sockets_wait(self, env: ExpEnv) -> tp.List[str]:
1095
1096
1097
1098
1099
        return super().sockets_wait(env) + [env.nic_eth_path(self)]


class NetMem(NetMemSim):

1100
    def run_cmd(self, env: ExpEnv) -> str:
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
        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