Modern networks are growing increasingly complex, with a multitude of devices and protocols. Understanding these in-depth is challenging and requires deep visibility into such systems, especially when physical prototypes are out of reach. Therefore engineers need tools to analyze and optimize such systems. Virtual prototyping enables users to create flexible software representations of such systems to provide the required visibility into such systems, as discussed in a previous post. However, creating these virtual prototypes is challenging, since we need to simulate all components involved that influence the systems behavior. In this blog post we look at how to easily create virtual prototypes of such systems with SimBricks.

Virtual Prototyping of Complex Network Systems

In the following we’ll explore how SimBricks’ simplifies virtual prototyping for complex systems through four milestones. Throughout these milestones, we will progressively increase the system’s complexity. We will showcase how you can leverage the SimBricks orchestration framework’s simple Python scripts to define the system topology, simulators to use, as well as drivers and applications to run. Each milestone has a corresponding demo.py script that you can find in the milestones subfolder of the networking case study in our examples repository. Explore the milestones firsthand and sign up for our demo which requires no setup on your end! In the demo you can simply run simbricks-run --force --verbose demo.py to start a simulation.

Milestone 1: Client-Server Pair and Switch Network

Simple system: client and server connected via a switch

We start with a virtual prototype of a basic system composed of a client and a server, each connected to a NIC in turn connected by a switch as shown in the figure above. We will begin by creating an experiment:

experiments = []
e = exp.Experiment("qemu-Host")
experiments.append(e)

Then, we instantiate a network switch that will later connect the client and server NICs with each other:

net = sim.SwitchNet()
# …
e.add_network(net)

We proceed by creating an instance of a behavioral model of the Corundum open-source NIC and attach it to the network we created before:

nic = sim.CorundumBMNIC()
# …
nic.set_network(net)
e.add_nic(nic)

SimBricks uses configuration classes for both hardware and software components. We define the server using a node config class, specifying its operating system (Linux in this case), the application to run (netperf for testing), and the required drivers (for the Corundum NIC). SimBricks includes classes for simulators and configurations that users can easily extend to create custom configurations. Next, we instantiate QEMU as the host simulator and establish a connection between the server and the previously created NIC:

node_config = node.CorundumLinuxNode()
# …
node_config.app = node.NetperfServer()
server = sim.QemuHost(node_config)
# …
server.add_nic(nic)
e.add_nic(nic)
e.add_host(server)

To simplify this process, SimBricks offers helper functions like create_basic_hosts(...). In our example we use this to create the client host alongside its NIC.

Milestone 2: Dumbbell Topology and HDL level NIC simulation

Dumbbell topology with corundum verilator NICs

This milestone introduces a more intricate scenario - a “dumbbell” topology. We replace our single switch by a predefined network with two switches connected by a limited bandwidth link to introduce a bottleneck. Additionally, we switch from the behavioral Corundum model to a detailed RTL hardware simulation using Verilator. This allows for a more accurate representation of the NIC’s internal workings and makes our experiment setup more realistic. To model the network, we leverage a Python wrapper around the ns-3 network simulator provided by SimBricks:

net = sim.NS3E2ENet()
topology = E2EDumbbellTopology()
# …
net.add_component(topology)
e.add_network(net)
# …
for client_index, client in enumerate(clients, 1):
   # …   
   helpers.add_host_to_topo_right(topology, cn, client.nics[0], synchronized)

For switching the NIC model, we simply replace the sim.CorundumBMNIC class instantiation in our experiment script with sim.CorundumVerilatorNIC. If you are curious about how SimBricks incorporates simulators like Verilator, check out this blog post.

Milestone 3: Multiple Client-Server Pairs

Topology with multiple client server pairs

In this milestone we will create multiple client-server pairs, showcasing how to easily build larger topologies with various hosts and their simulators. We will also explore using different simulators for these pairs. For each client-server pair we will create either two (client and server) gem5 or two QEMU instances. SimBricks lets you represent diverse hosts within your simulated system allowing you to seamlessly mix detailed simulators like gem5, which model hardware behavior in great detail, alongside lighter-weight emulators like QEMU in the same virtual prototype.

You can easily do all of this using standard Python language constructs:

# (number of client-server pairs, simulator type)
hos_conf = [(1, "qt"), (1, "gt")]
clients = []
for amount, sim_type in hos_conf:
   clients.extend(
       create_basic_hosts(...)
   )

Milestone 4: Adding Background Traffic

Topology with ns-3 packet source and sink for traffic generation

In our final milestone, we introduce background traffic on the bottleneck link of the dumbbell topology. To achieve this without the overhead of adding more full-system simulators and detailed NIC models, we use packet sources and sinks in the ns-3 simulator. These act as “dummy” hosts, generating and absorbing traffic without requiring expensive, in-depth simulation. This approach exemplifies a mixed-fidelity simulation strategy. By selectively employing different levels of simulation detail for various components, you can tailor your virtual prototypes to specific needs and maintain a balance between accuracy and efficiency. We again use the SimBricks ns-3 Python wrapper to create these “dummy” hosts:

for i in range(1, num_ns3_hosts + 1):
   host = e2e.E2ESimpleNs3Host(f"ns3server-{i}")
   # …
   app = e2e.E2EPacketSinkApplication("sink")
   # …
   host.add_component(app)
   topology.add_left_component(host)


for i in range(1, num_ns3_hosts + 1):
   host = e2e.E2ESimpleNs3Host(f"ns3client-{i}")
   # …
   app = e2e.E2EBulkSendApplication("sender")
   # …
   host.add_component(app)
   topology.add_right_component(host)

Want to learn more about SimBricks? Check out the rest of our website. If you have any questions or comments, please do not hesitate to contact us: