Chapter 5: Source Internals

Source Deep Dive: Port Binding and Interface Dispatch

How sc_port, sc_export, sc_interface, and channels connect modules during elaboration.

Port binding is where SystemC turns a C++ object hierarchy into a connected model graph. The implementation must answer what interface is required, what object provides it, and if the binding is complete.

Interfaces, Ports, and Channels

An interface derives from sc_interface. A port requires an interface. A channel implements the interface. Binding must happen before simulation time begins, allowing the kernel to validate the structural integrity.

Here is a complete, compilable example illustrating how an sc_port resolves a C++ virtual function call to an sc_interface implemented by an sc_export mapping to a channel.

#include <systemc>
#include <iostream>
 
using namespace sc_core;
 
// 1. The Interface Contract
struct BusIf : public sc_interface {
  virtual uint32_t read(uint64_t address) = 0;
  virtual void write(uint64_t address, uint32_t data) = 0;
};
 
// 2. The Channel Implementation
class MemoryChannel : public sc_module, public BusIf {
public:
  SC_CTOR(MemoryChannel) {}
 
  uint32_t read(uint64_t address) override {
    std::cout << "Memory handling READ at " << std::hex << address << "\n";
    return 0xCAFE;
  }
  void write(uint64_t address, uint32_t data) override {
    std::cout << "Memory handling WRITE at " << std::hex << address << " data: " << data << "\n";
  }
};
 
// 3. The Subsystem Exposing the Channel via sc_export
SC_MODULE(MemorySubsystem) {
  sc_export<BusIf> target{"target"};
  MemoryChannel memory{"memory"};
 
  SC_CTOR(MemorySubsystem) {
    // Export binds to the internal channel implementation
    target.bind(memory);
  }
};
 
// 4. The Initiator Using the Channel via sc_port
SC_MODULE(CpuModel) {
  sc_port<BusIf> bus{"bus"};
 
  SC_CTOR(CpuModel) { SC_THREAD(run); }
 
  void run() {
    // The operator-> provides access to the bound interface
    // The call becomes a virtual function call on the channel implementation
    bus->write(0x1000, 0x1234);
    uint32_t val = bus->read(0x1000);
  }
};
 
int sc_main(int argc, char* argv[]) {
  CpuModel cpu("cpu");
  MemorySubsystem subsystem("subsystem");
 
  // Elaboration phase: Structural binding
  cpu.bus.bind(subsystem.target);
 
  sc_start(); // Simulation phase
  return 0;
}

Binding During Elaboration

Binding happens before simulation. That matters because the kernel can validate topology before any process runs. At the end of elaboration, the kernel checks that required ports are bound and that binding policies are satisfied.

Calling Through a Port

Once bound, the module can call through the port using operator->. The useful abstraction: structural binding is checked during elaboration, while actual behavior is ordinary C++ interface dispatch (virtual function calls) during simulation.

How This Relates to TLM Sockets

TLM sockets are higher-level wrappers around the exact same ideas. An initiator socket contains a port to a forward transport interface. A target socket exposes an export for that interface and dispatches calls to registered callbacks. The magic is packaging, not a different universe.

Comments and Corrections