SystemC Source Map & LRM Alignment
A guided map of the official source tree: kernel, communication, datatypes, TLM, and utilities.
How to Read This Lesson
This is a source-reading lesson. We will use the Accellera implementation as a microscope, while keeping the LRM as the portability contract.
SystemC Source Map & LRM Alignment
The official SystemC implementation is worth reading because it explains the API design. You do not need to read it linearly. Read it as a map of responsibilities, aligned directly with the IEEE 1666 Language Reference Manual (LRM).
The public repository is accellera-official/systemc. Directory names can move across releases, but the conceptual areas remain stable. Understanding how these conceptual areas map to the C++ implementation is the key to deep SystemC mastery.
Source and LRM Trail
This lesson is deliberately source-facing. Use Docs/LRMs/SystemC_LRM_1666-2023.pdf to decide what must be portable, then use .codex-src/systemc/src/sysc and .codex-src/systemc/src/tlm_core to see one reference implementation. Treat private members as explanatory, not as APIs your models should depend on.
1. Kernel (LRM Section 4: Elaboration and Simulation)
The kernel area contains the scheduler, simulation context, process classes, events, time, reports, and object hierarchy. Names you will encounter include:
sc_simcontext: the central simulation context. It holds the event queues and Delta Cycle loop.sc_object: the base class for hierarchical objects.sc_module: structural hierarchy and process registration support.sc_event: event notification and wait support.sc_process_b,sc_method_process,sc_thread_process: implementation of methods and coroutines.
Read this area when you want to understand sc_start(), delta cycles, process readiness, and why wait() is legal in an SC_THREAD but throws an exception in an SC_METHOD.
2. Communication (LRM Section 6 & 7: Channels and Interfaces)
The communication area contains ports, exports, interfaces, primitive channels, signals, FIFOs, mutexes, semaphores, and clocks.
sc_interface: Pure virtual base class.sc_port: The mechanism for a module to call an interface.sc_signal: The fundamental channel implementing request-update semantics.
This is where the object model becomes a network. Ports do not merely hold C++ pointers. They participate in binding policy, interface checking, and simulation setup.
3. Datatypes (LRM Section 7: Data Types)
SystemC includes hardware-friendly datatypes such as:
- Arbitrary precision integers (
sc_bigint,sc_biguint) - Bit vectors (
sc_bv) - Logic vectors (
sc_lv,sc_logic) - Fixed-point types (
sc_fixed)
These types exist because plain C++ integer behavior cannot model hardware realities like four-state logic ('0', '1', 'Z', 'X') or precise bit-width truncation.
4. TLM (LRM Section 10-16: TLM-2.0)
The TLM area includes generic payloads, phases, sockets, target and initiator interfaces, and utility helpers. Sockets are not a separate universe. They package standard SystemC ports, exports, callbacks, and interfaces into a highly optimized bus-modeling form.
Bringing it All Together: Complete Example
To illustrate how these four main quadrants of the SystemC source map (Kernel, Communication, Datatypes, TLM) synthesize into a single LRM-compliant model, here is a complete executable example.
#include <systemc>
#include <tlm>
#include <tlm_utils/simple_initiator_socket.h>
#include <tlm_utils/simple_target_socket.h>
#include <iostream>
using namespace sc_core;
using namespace sc_dt; // Datatypes
// ---------------------------------------------------------
// A module demonstrating Kernel processes, Communication
// channels, Datatypes, and TLM-2.0 Sockets.
// ---------------------------------------------------------
SC_MODULE(SourceMapDemo) {
// --- COMMUNICATION ---
sc_in<bool> clk{"clk"};
// --- COMMUNICATION & DATATYPES ---
// A primitive channel wrapping a 4-bit unsigned integer
sc_signal<sc_uint<4>> counter{"counter"};
// --- TLM ---
tlm_utils::simple_initiator_socket<SourceMapDemo> init_socket{"init_socket"};
SC_CTOR(SourceMapDemo) {
// --- KERNEL ---
// Registering a clocked thread with the simulation context
SC_CTHREAD(clocked_thread, clk.pos());
}
void clocked_thread() {
// Initialization
counter.write(0);
while (true) {
// Kernel: Yield execution back to the scheduler
wait();
// Datatypes: Read, manipulate, and write the 4-bit type
sc_uint<4> val = counter.read() + 1;
counter.write(val);
std::cout << "[Demo] Clock ticked. Counter: " << val << "\n";
// TLM: Generate a transaction when the counter overflows (4-bit overflow)
if (val == 0) {
std::cout << "[Demo] Counter overflowed! Sending TLM transaction.\n";
tlm::tlm_generic_payload trans;
sc_time delay = SC_ZERO_TIME;
trans.set_command(tlm::TLM_WRITE_COMMAND);
trans.set_address(0x100);
trans.set_data_length(4);
trans.set_streaming_width(4);
trans.set_response_status(tlm::TLM_INCOMPLETE_RESPONSE);
// Blocking transport call
init_socket->b_transport(trans, delay);
if (trans.is_response_ok()) {
std::cout << "[Demo] TLM transaction completed successfully.\n";
}
}
}
}
};
// Target to satisfy the TLM binding
SC_MODULE(DummyTarget) {
tlm_utils::simple_target_socket<DummyTarget> target_socket{"target_socket"};
SC_CTOR(DummyTarget) {
target_socket.register_b_transport(this, &DummyTarget::b_transport);
}
void b_transport(tlm::tlm_generic_payload& trans, sc_time& delay) {
std::cout << " -> [Target] Received transaction at address 0x"
<< std::hex << trans.get_address() << "\n";
trans.set_response_status(tlm::TLM_OK_RESPONSE);
}
};
// Top Level
int sc_main(int argc, char* argv[]) {
// Kernel: Clock generation
sc_clock clk("clk", 10, SC_NS);
SourceMapDemo demo("demo");
DummyTarget tgt("tgt");
// Communication: Structural binding during Elaboration Phase
demo.clk(clk);
demo.init_socket.bind(tgt.target_socket);
std::cout << "Starting Simulation...\n";
// Kernel: Enter Simulation Phase
sc_start(170, SC_NS);
std::cout << "Simulation Complete.\n";
return 0;
}Explanation of the Execution
When you run this simulation, the sc_clock drives the SC_CTHREAD. The sc_uint<4> datatype safely rolls over from 15 to 0. When it hits 0, it triggers the TLM-2.0 socket, bridging the cycle-accurate RTL abstraction with the Loosely Timed VP abstraction.
By understanding how these source map quadrants interact, you can read any SystemC codebase with confidence.
Under the Hood: The Accellera Kernel Directory Map
When debugging a SystemC error, knowing the Accellera source tree is your superpower:
src/sysc/kernel/sc_simcontext.cpp: Contains thecrunch()function, the beating heart of the SystemC scheduler.src/sysc/kernel/sc_process.cpp: Manages process state machines (runnable, waiting, dead).src/sysc/qt/: The QuickThreads coroutine library used forSC_THREADcontext switching.src/sysc/communication/sc_signal.cpp: Thesc_signaltemplate logic andsc_prim_channelupdate mechanisms.src/sysc/datatypes/int/: Implementations of bit-accurate integers.src/tlm_core/tlm_2/tlm_sockets/: Implementation oftlm_initiator_socketandtlm_target_socket, which are essentially convenience wrappers combining ansc_portand ansc_export.
Standard and Source Deep Dive: Port Binding
Port binding is the topological glue of a SystemC model. The IEEE 1666-2023 LRM Sections 4.2.1 (Elaboration) and Section 6.11-6.13 (Ports, Exports, Interfaces) rigidly define how structural connections are made and verified.
Inside the Accellera Source: sc_port_b and sc_port_registry
In src/sysc/communication/sc_port.h/cpp, all specialized sc_port<IF> classes derive from a non-template base class sc_port_b.
When you declare sc_port<BusIf> bus{"bus"};, the constructor ultimately calls sc_simcontext::get_port_registry()->insert(this).
The sc_port_registry (located in src/sysc/kernel/sc_simcontext.cpp) is the global list of every port in the simulation.
When you write cpu.bus.bind(subsystem.target); in your C++ code, you are invoking the bind() method on sc_port. However, this does not immediately resolve the C++ pointer! Instead, the port simply stores a generic pointer to the bound object in an internal array (because a port can be bound to multiple channels if the port's N parameter is > 1).
The Elaboration Phase: complete_binding()
The real magic happens when sc_start() is called.
Before simulation begins, sc_start() invokes sc_simcontext::elaborate(), which ultimately calls sc_port_registry::complete_binding().
If you trace sysc/kernel/sc_simcontext.cpp, you will see complete_binding() iterate over every single port in the design. For each port:
- It traverses the binding tree. If Port A is bound to Port B, and Port B is bound to Channel C, it recursively walks from A -> B -> C to find the actual
sc_interfaceimplementation. - Type Checking: It uses C++ RTTI (
dynamic_cast) to verify that the target object actually implements the interface required by the port.// Abstract representation of the kernel's check: sc_interface* target_if = dynamic_cast<sc_interface*>(bound_object); if (!target_if) { SC_REPORT_ERROR("Port binding failed: interface mismatch"); } - It resolves the final interface pointer and stores it directly inside the port's
m_interfacepointer array.
Zero-Overhead Simulation Dispatch
Why delay pointer resolution until complete_binding()? Because once elaboration finishes, the port has an absolute, direct C++ pointer to the implementing channel.
In src/sysc/communication/sc_port.h, the overloaded operator-> is extraordinarily simple:
template <class IF>
inline IF* sc_port<IF>::operator -> () {
return m_interface;
}During simulation, when a thread executes bus->write(0x10, data);, there are no map lookups, no string comparisons, and no routing tables. It is exactly equivalent to a direct C++ virtual function call on the channel object.
Comments and Corrections