A Practical SystemC Source-Reading Workflow
How to move from public API behavior to the implementation without getting lost in templates.
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.
Reading SystemC source is not like reading a small application. It is a mature C++ library with compatibility concerns, macros, templates, platform code, and multiple abstraction layers. You need a workflow.
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.
Start From a Tiny Example
Pick one behavior:
SC_METHOD(comb);
sensitive << a << b;Ask one question: how does this method become runnable when a changes?
Now follow only the code needed for that question. Ignore unrelated template specializations until they become necessary.
Trace Public API to Internal Object
For any feature, identify:
- The public type or macro the user writes.
- The object created by that API.
- The simulation context it registers with.
- The callback or virtual function invoked later.
- The event or update that causes progress.
This turns source reading into a graph walk rather than a file-reading marathon.
Use Search Terms That Match Concepts
Useful searches:
rg "request_update" src
rg "notify" src/sysc
rg "sc_simcontext" src
rg "SC_METHOD" src
rg "b_transport" srcWhen a term is too broad, search for a public method name plus a class name.
Read Header First, Implementation Second
Headers show the API and object relationships. Implementation files show scheduling and policy.
For example, with signals:
- Read the signal class declaration.
- Find
write(). - Find where it requests an update.
- Find
update(). - Find where value-change events are notified.
That sequence explains user-visible behavior.
Keep a Source Notebook
For each source reading session, write down:
- public API
- internal type
- key member fields
- important callbacks
- scheduler interaction
- surprising edge cases
This site can grow those notes into new lessons. The best technical tutorials are not just explanations; they are maps of what the reader should inspect next.
Exhaustive Deep Dive: Mapping the IEEE 1666 LRM to Accellera Source Architecture
Navigating the SystemC source effectively requires understanding how the IEEE 1666-2023 LRM specification maps to the directory structure and class hierarchy of the Accellera reference implementation. The standard dictates the what and when, while the reference implementation dictates the how.
The LRM as the Ultimate Blueprint
The SystemC Standard is rigorously structured. When tracing a behavior, the first step is to locate the relevant LRM section:
- Section 4 (Elaboration and Simulation Semantics): Defines the phases of execution (
sc_start, initialization, evaluate, update, delta cycles). - Section 5 (Processes): Defines
SC_METHOD,SC_THREAD,SC_CTHREAD, static/dynamic sensitivity, and thewait()semantics. - Section 6 (Core Language and Data Types): Defines
sc_module, ports, interfaces, and primitive channels (e.g.,sc_signal,sc_fifo). - Section 10-16 (TLM-2.0): Covers sockets, payloads, generic transport interfaces, and the Payload Event Queue (PEQ).
Whenever a behavior in the Accellera source seems convoluted, cross-referencing these chapters usually reveals that the complexity exists strictly to satisfy an edge-case compliance rule mandated by the LRM.
The Accellera Source Directory Map
If you cd into the Accellera SystemC repository, the code is primarily divided into two main branches: src/sysc (Core SystemC) and src/tlm_core (Transaction Level Modeling).
1. src/sysc/kernel (The Heart of the Simulator)
This is where the magic happens. Key files include:
sc_simcontext.cpp: The central engine. It manages the simulation time (sc_time_stamp), the list of all created objects (m_object_manager), and the event queues.sc_runnable.cpp/sc_runnable.h: Manages the lists of methods and threads that are ready to execute in the current delta cycle.sc_module.cpp/sc_module_name.cpp: Contains the logic for the structural hierarchy built during elaboration. The intricatesc_module_nameclass is responsible for pushing and popping module hierarchies onto the simulation context's internal stack so that nested modules get the correct hierarchical string names.sc_event.cpp: Implements the notification semantics (immediate, delta-delayed, timed).
2. src/sysc/communication (Ports and Channels)
This directory implements the core interfaces defined in LRM Section 6.
sc_signal.cpp/sc_clock.cpp: Primitive channels implementing the evaluate-update paradigm.sc_port.cpp/sc_export.cpp: The structural binding mechanics.
3. src/sysc/qt (QuickThreads)
SystemC relies on user-space threading (coroutines) to implement SC_THREAD. The qt (QuickThreads) directory contains the assembly code (md/ machine-dependent subdirectory) required to save and restore CPU registers, perform stack unwinding, and context-switch between threads in mere nanoseconds.
Decompiling the Macros: SC_MODULE and SC_CTOR
A classic source-reading challenge is deciphering the omnipresent macros. According to src/sysc/kernel/sc_module.h:
#define SC_MODULE(user_module_name) \
struct user_module_name : ::sc_core::sc_moduleThis simply translates to inheriting from the core sc_module class. However, SC_CTOR is more complex:
#define SC_CTOR(user_module_name) \
typedef user_module_name SC_CURRENT_USER_MODULE; \
user_module_name( ::sc_core::sc_module_name )When you write SC_CTOR(MyModule), it declares a constructor taking an sc_module_name object. The creation of sc_module_name triggers a constructor inside the Accellera kernel that registers the new module in the sc_simcontext hierarchy stack. This is why you cannot easily instantiate an sc_module without passing a string name; the kernel depends on the sc_module_name temporary object to hook into the elaboration tree.
Tracing Process Registration (SC_METHOD / SC_THREAD)
When you declare an SC_METHOD(func), what actually executes?
In src/sysc/kernel/sc_process_macros.h, SC_METHOD expands to an invocation of sc_core::sc_module::declare_method_process().
This function takes:
- The name of the function.
- A macro-generated wrapper
sc_method_handle(often usingSC_MAKE_FUNC_PTR). - The host object (
this).
The kernel then allocates an sc_method_process object (defined in sc_method_process.h/cpp), sets its state to UNINITIALIZED, and registers it with the sc_simcontext. During the initialization phase (LRM 4.2.1.1), the sc_simcontext iterates over all registered process objects and pushes them onto the runnable queue (unless dont_initialize() was called).
Doxygen, ctags, and LSPs
Because SystemC relies heavily on macros and C++ templates, standard text searching (grep / rg) can sometimes fall short.
Professional SystemC architects heavily rely on:
- Doxygen: The Accellera repository includes a Doxygen configuration file. Generating the documentation yields a highly navigable HTML tree of the class hierarchies.
- clangd / LSP: Modern Language Servers can expand the macros in real-time. If you encounter an opaque type like
sc_event_or_list, usingGo to Definitionvia an LSP will immediately drop you intosysc/kernel/sc_event.h, showing exactly how dynamic event sensitivity is implemented using linked lists.
By mapping the rigid rules of the IEEE 1666 LRM to the highly optimized C++ structures of the Accellera source code, the seemingly opaque behavior of SystemC transforms into a readable, deterministic C++ architecture.
Comments and Corrections