Chapter 7: SystemC 1666-2023 LRM

LRM Bridge: Ports, Exports, Interfaces, and Channels

The standard communication model: sc_interface, sc_port, sc_export, sc_prim_channel, sc_channel, and binding policies.

SystemC hardware communication is strictly interface-based, utilizing a separation of concerns defined by the LRM.

The Four Pillars of Communication

  1. sc_interface: An abstract C++ class defining what can be done (e.g., read(), write()). It contains no implementation.
  2. Channel (sc_channel or sc_prim_channel): A module that implements the interface. It contains the actual state and logic.
  3. sc_port: An outward-facing connection on a module that requires an interface to function.
  4. sc_export: An inward-facing connection on a module boundary that provides an interface implemented by a child module.

End-to-End Interface and Channel Example

This fully compliant IEEE 1666 example demonstrates defining an interface, implementing a hierarchical channel, and binding a port to it.

#include <systemc>
 
// 1. Define the Interface (inheriting from sc_interface)
struct RegisterIf : virtual public sc_core::sc_interface {
    virtual uint32_t read(uint32_t offset) = 0;
    virtual void write(uint32_t offset, uint32_t data) = 0;
};
 
// 2. Implement the Channel (inheriting from sc_channel and the interface)
class RegisterBank : public sc_core::sc_channel, public RegisterIf {
private:
    uint32_t memory[256];
 
public:
    SC_HAS_PROCESS(RegisterBank);
    RegisterBank(sc_core::sc_module_name name) : sc_core::sc_channel(name) {
        for (int i = 0; i < 256; i++) memory[i] = 0;
    }
 
    uint32_t read(uint32_t offset) override {
        if (offset < 256) {
            std::cout << "@" << sc_core::sc_time_stamp() << " [Bank] Read 0x" 
                      << std::hex << memory[offset] << " from " << offset << std::endl;
            return memory[offset];
        }
        return 0;
    }
 
    void write(uint32_t offset, uint32_t data) override {
        if (offset < 256) {
            std::cout << "@" << sc_core::sc_time_stamp() << " [Bank] Wrote 0x" 
                      << std::hex << data << " to " << offset << std::endl;
            memory[offset] = data;
        }
    }
};
 
// 3. Define a Module requiring the interface via a Port
SC_MODULE(CPU_Model) {
    // Requires a RegisterIf implementation
    sc_core::sc_port<RegisterIf> regs{"regs"}; 
 
    SC_CTOR(CPU_Model) {
        SC_THREAD(execute_logic);
    }
 
    void execute_logic() {
        wait(10, sc_core::SC_NS);
        regs->write(0x10, 0xDEADBEEF); // Accesses the channel via the port interface
        
        wait(10, sc_core::SC_NS);
        uint32_t val = regs->read(0x10);
    }
};
 
// 4. Encapsulate with an Export
SC_MODULE(Subsystem) {
    // Exposes the internal RegisterBank to the outside world
    sc_core::sc_export<RegisterIf> target_export{"target_export"};
    RegisterBank regs{"regs"};
 
    SC_CTOR(Subsystem) {
        // Bind the export to the internal channel
        target_export.bind(regs);
    }
};
 
int sc_main(int argc, char* argv[]) {
    CPU_Model cpu("cpu");
    Subsystem subsys("subsys");
 
    // Bind the CPU's port to the Subsystem's export
    cpu.regs.bind(subsys.target_export);
 
    sc_core::sc_start(50, sc_core::SC_NS);
    return 0;
}

Primitive vs Hierarchical Channels

  • Primitive Channels (sc_prim_channel): Used for fundamental data types (like sc_signal). They hook directly into the Evaluate-Update paradigm using the request_update() and update() virtual methods. They cannot have structural hierarchy (child modules) or SC_THREADs.
  • Hierarchical Channels (sc_channel): Modules that implement an interface (as shown in the example above). They can contain internal processes (SC_THREAD), ports, and child modules.

Binding Policies

The LRM enforces strict binding constraints during the elaboration phase. sc_port can accept a binding policy:

  • SC_ONE_OR_MORE_BOUND: The port must be bound at least once (default).
  • SC_ZERO_OR_MORE_BOUND: The port may remain unbound (useful for optional interrupts).
  • SC_ALL_BOUND: All elements of a multi-port array must be bound.

If a connection executes behavior, model it as an interface and channel. If a connection crosses structural hierarchy, expose it cleanly with an export.

Comments and Corrections