Thanks for meeting me, SystemC!
Table of Contents
- Introduction to SystemC
- Key Components of SystemC
- Transaction-Level Modeling (TLM)
- SystemC Simulation Kernel
- SystemC Verification & Debugging
- Practical SystemC
Introduction to SystemC Link to heading
SystemC is a C++ based modeling framework for designing and simulating hardware at various abstraction levels. It provides:
- Hardware-like concurrency using processes.
- Event-driven simulation similar to HDLs (e.g., Verilog, VHDL).
- Transaction-Level Modeling (TLM) for system-level communication.
- Data types for precise bit-accurate modeling.
SystemC is standardized as IEEE 1666 and is widely used in system-level modeling, architecture exploration, verification, and virtual prototyping.
Key Components of SystemC Link to heading
Modules (sc_module) Link to heading
SystemC models are built using modules, which encapsulate functionality.
SC_MODULE(MyModule) {
SC_CTOR(MyModule) {
// Constructor
}
};
ℹ️ Modules can contain processes, ports, and internal signals.
Processes (SC_METHOD
, SC_THREAD
, SC_CTHREAD
)
Link to heading
SystemC supports three types of processes for concurrency:
SC_METHOD
: Executes instantaneously (no wait).SC_THREAD
: Executes with wait() calls (suspends and resumes).SC_CTHREAD
: Specialized for clocked behavior (synthesizable).
Example:
SC_MODULE(Test) {
SC_CTOR(Test) {
SC_METHOD(myMethod);
sensitive << clk.pos();
SC_THREAD(myThread);
sensitive << clk.pos();
}
void myMethod() {
std::cout << "Method executed" << std::endl;
}
void myThread() {
while (true) {
wait();
std::cout << "Thread executing at time " << sc_time_stamp() << std::endl;
}
}
sc_in<bool> clk;
};
SC_METHOD
executes once at time t=0
when the simulation starts. However if dont_initialize()
is used, it prevents this initial execution and ensures the method only runs when a triggering event occurs.
⭐
dont_initialize()
only prevents execution at time 0 when the process is created in the constructor.
✅ Example:
SC_METHOD(myMethod);
sensitive << clk.pos();
dont_initialize(); // Prevents execution at time 0
Without
dont_initialize()
,SC_METHOD
runs once at time 0. Important in register-based modeling where the initial state must remain unchanged.
Events (sc_event) Link to heading
sc_event
allows synchronization between processes.
Triggering events:
my_event.notify();
Waiting for events:
wait(my_event);
A simple example:
sc_event my_event;
SC_THREAD(myProcess) {
while (true) {
wait(my_event);
std::cout << "Event triggered!" << std::endl;
}
}
SystemC Data Types Link to heading
SystemC provides hardware-specific data types for precision:
- Fixed-width integers:
sc_int<8>, sc_uint<16>
- Big integers:
sc_bigint<128>, sc_biguint<256>
- Fixed-point types:
sc_fixed<8,4>, sc_ufixed<16,8>
- Bit vectors:
sc_bv<32>, sc_lv<64>
(logic values)
Example:
sc_uint<8> a = 255;
sc_bv<4> b = "1010";
Reset Handling in SystemC Link to heading
- Many designs require asynchronous resets for initialization.
SC_CTHREAD
requires explicit reset handling.
✅ Example:
SC_CTHREAD(myProcess, clk.pos());
reset_signal_is(rst, true); // Active-high reset
This ensures the process resets properly when rst goes high.
Without reset_signal_is()
, the process won’t reset correctly in simulation.
✅ Where is this useful?
- Packet buffers → Resetting router states.
- Pipeline stages → Resetting computation logic.
❗❗reset_signal_is()
vs. async_reset_signal_is()
Both configure resets for SC_CTHREAD, but they behave differently.
reset_signal_is()
(Synchronous Reset), resets the process only on a clock edge when the reset signal is active. It requires at least one clock cycle before taking effect.
async_reset_signal_is()
(Asynchronous Reset) resets the process immediately, without waiting for a clock edge. The process stops execution as soon as reset is asserted.
🚀 Key Differences Between reset_signal_is()
and async_reset_signal_is()
Feature | reset_signal_is() (Synchronous) | async_reset_signal_is() (Asynchronous) |
---|---|---|
Reset Behavior | Happens on the next clock edge | Happens immediately |
Clock Dependency? | ✅ Yes | ❌ No |
Execution Stops? | ❌ No (process continues running until reset is triggered on clock edge) | ✅ Yes (process stops execution immediately) |
Use Case | Registers, FSMs that reset on clock edges | Asynchronous resets for NoC buffers or pipeline stalls |
❓ When to Use Which?
✅ Use reset_signal_is()
for synchronous circuits (e.g., pipeline registers, NoC buffers).
✅ Use async_reset_signal_is()
when reset must happen immediately (e.g., hardware reset pins in an SoC).
👽 Note on async_reset_signal_is()
:
- If a process is already waiting (wait() state),
async_reset_signal_is()
resets immediately. - If a process is actively executing, SystemC does not interrupt it mid-execution—the reset takes effect before the next wait().
Transaction-Level Modeling (TLM) Link to heading
TLM is used for abstract communication between modules in SystemC. Traditional modeling: Pin-level, cycle-accurate. TLM modeling: Data transfer using function calls (higher-level abstraction).
TLM Interfaces Link to heading
TLM uses interfaces and sockets for communication.
TLM-1 (Untimed / Loosely Timed) Uses blocking (b_transport) and non-blocking (nb_transport).
TLM-2.0 (Modern Standard) Uses sockets (tlm_initiator_socket / tlm_target_socket). Implements Generic Payload (tlm_generic_payload).
TLM-2.0 Communication Link to heading
TLM-2.0 defines two transport interfaces:
- Blocking transport (b_transport) → Function call, completes before returning.
- Non-blocking transport (nb_transport) → Separate request/response phases.
- TLM-2.0 includes four phases in non-blocking transport:
- BEGIN_REQ (Request issued)
- END_REQ (Request accepted)
- BEGIN_RESP (Response issued)
- END_RESP (Response completed)
- TLM-2.0 includes four phases in non-blocking transport:
Example:
#include <systemc>
#include <tlm>
#include <tlm_utils/simple_target_socket.h>
SC_MODULE(Target) {
tlm_utils::simple_target_socket<Target> socket;
SC_CTOR(Target) : socket("socket") {
socket.register_b_transport(this, &Target::b_transport);
}
void b_transport(tlm::tlm_generic_payload& trans, sc_time& delay) {
std::cout << "TLM Transaction received at " << sc_time_stamp() << std::endl;
}
};
SystemC Simulation Kernel Link to heading
Simulation Phases Link to heading
- Elaboration – Object construction (SC_CTOR execution).
- Initialization – Static sensitivity is set up.
- Simulation – sc_start() runs event-driven simulation.
- Termination – sc_stop() halts simulation.
Scheduling Mechanism Link to heading
SystemC follows delta-cycle scheduling:
- Immediate events execute in the same simulation cycle.
- Timed events advance simulation time.
Delta-cycles are zero-time steps used to resolve dependencies without advancing the simulation time.
🔹 Why Do We Need Delta-Cycles? SystemC simulates digital circuits, and in hardware some events trigger other events without consuming real simulation time. For example, combinational logic propagates signals instantly, but SystemC processes these updates in multiple steps using delta-cycles. 💡 Key Idea: Delta-cycles resolve all signal updates before moving to the next actual time step.
SystemC Verification & Debugging Link to heading
Tracing (sc_trace) Link to heading
Dumps signals into a VCD file. ** Example:**
sc_trace_file *tf = sc_create_vcd_trace_file("waveform");
sc_trace(tf, my_signal, "my_signal");
Checkpoints & Assertions Link to heading
- Use
SC_REPORT_*
macros. sc_assert()
for assertions.sc_stop()
to halt simulation at a condition.- Use
sc_time_stamp()
to print timestamps.
ℹ️ Debugging SystemC models often requires assertion-based checks.
sc_assert(packet_size > 0 && packet_size <= MAX_SIZE);
ℹ️ Using SC_REPORT_ERROR()
for Debugging
SC_REPORT_ERROR("NoC", "Packet dropped due to congestion!");
Practical SystemC Link to heading
Tips and tricks for SystemC modeling. Link to heading
📌 What is the difference between SC_THREAD and SC_METHOD? SC_THREAD supports wait(), while SC_METHOD does not.
📌 Explain how TLM-2.0 differs from TLM-1. TLM-2.0 introduces generic payloads and sockets.
📌 How does SystemC handle concurrency? It uses event-driven simulation and delta-cycle scheduling.
📌 What are the advantages of TLM over RTL modeling? Faster simulation, early software development, and system-level exploration.
📌 How to use multi-clock domains in SystemC?
Heterogeneous architectures may have different clock domains (e.g., CPU vs. GPU interconnects).
sc_signal<bool>
cannot cross clock domains safely → Use sc_clock
instead.
✅ Example: Two Different Clocks
sc_clock clk1("clk1", 10, SC_NS); // 100 MHz clock
sc_clock clk2("clk2", 5, SC_NS); // 200 MHz clock
📌 Explain the concept of a delta cycle. A zero-time cycle where multiple events execute before time advances.
📌 How do you trace signals in SystemC? Using sc_trace() with a .vcd file.
📌 What is tlm_generic_payload? A standard transaction object in TLM-2.0 that holds address, data, and control info.
📌 How SystemC’s Scheduler Works? (Event-Driven Simulation Model) SystemC uses a three-phase scheduler:
Phase | What Happens? |
---|---|
Evaluation Phase | Runs all processes that are ready. |
Delta Notification Phase | Any new events trigger re-evaluation in the same simulation time (delta-cycle). |
Time Advancement Phase | If no further delta-cycles are needed, time moves forward. |
🚀 Example:
- At t=10 ns, an event triggers.
- The signal propagates combinational logic, requiring multiple updates.
- SystemC processes the updates using delta-cycles without moving beyond t=10 ns.
- Once updates are settled, the simulation advances to the next actual time step.
📌 What is the difference between SC_METHOD
and SC_THREAD
in terms of delta-cycles?
Feature | SC_METHOD |
SC_THREAD |
---|---|---|
Uses delta-cycles? | ✅ Yes | ❌ No |
Time moves forward? | ❌ No | ✅ Yes (requires wait() ) |
Suitable for combinational logic? | ✅ Yes | ❌ No |
Suitable for sequential logic? | ❌ No | ✅ Yes |
📌 What Is a Clocked Process in SystemC?
SC_CTHREAD
(Clocked Thread Process) is a special type of SC_THREAD
that is:
✅ Triggered on every clock edge (rising or falling).
✅ Synchronous → Executes at fixed intervals based on a clock.
✅ Cannot use sensitive « (sensitivity is controlled by the clock).
✅ Must include wait()
inside the function to avoid infinite loops. If reset is used, SC_CTHREAD
must wait for reset before execution. Without reset_signal_is()
, SC_CTHREAD
won’t reset properly in simulation.
🚀 Why Do We Need Clocked Processes? In hardware design many components operate on a clock signal (e.g., registers, pipelines, buffers).
- Combinational logic → Uses
SC_METHOD
(instantaneous updates). - Sequential logic → Uses
SC_CTHREAD
(clocked updates).
Example: SC_CTHREAD
for a Counter
#include <systemc.h>
SC_MODULE(Counter) {
sc_in<bool> clk;
sc_out<int> count;
void countProcess() {
count.write(0); // Initialize count to 0
wait(); // Wait for the first clock edge
while (true) {
count.write(count.read() + 1); // Increment on each clock cycle
wait(); // Wait for the next clock edge
}
}
SC_CTOR(Counter) {
SC_CTHREAD(countProcess, clk.pos()); // Trigger on rising clock edge
}
};
int sc_main(int argc, char* argv[]) {
sc_signal<bool> clk;
sc_signal<int> count;
Counter counter("Counter");
counter.clk(clk);
counter.count(count);
// Simulate clock
for (int i = 0; i < 5; ++i) {
clk.write(1);
sc_start(1, SC_NS);
clk.write(0);
sc_start(1, SC_NS);
std::cout << "Time: " << sc_time_stamp() << ", Count: " << count.read() << "\n";
}
return 0;
}
✅ Expected Output:
Time: 2 ns, Count: 1
Time: 4 ns, Count: 2
Time: 6 ns, Count: 3
Time: 8 ns, Count: 4
Time: 10 ns, Count: 5
SC_CTHREAD(countProcess, clk.pos())
Process executes on every positive edge of clk.wait();
Waits for the next clock cycle before executing the next iteration.
📌 SC_CTHREAD
vs. SC_THREAD
vs. SC_METHOD
Feature | SC_CTHREAD |
SC_THREAD |
SC_METHOD |
---|---|---|---|
Triggered By | Clock edge (clk.pos() or clk.neg()) | Any signal event (sensitive «) | Any signal event (sensitive «) |
Time Advances? | ✅ Yes (Requires wait()) | ✅ Yes (Requires wait()) | ❌ No (Executes in delta-cycles) |
Sensitivity | Clock signal only | Manual (sensitive «) | Manual (sensitive «) |
Use for Combinational Logic? | ❌ No | ❌ No | ✅ Yes |
Use for Sequential Logic? | ✅ Yes | ✅ Yes | ❌ No |
🚀 When to Use Which?
- Use
SC_METHOD
for combinational logic (no time progression). - Use
SC_THREAD
for sequential processes that run asynchronously. - Use
SC_CTHREAD
for synchronous clock-driven logic.
✨ Simple ✨
📌 How to use sc_fifo
?
sc_fifo<T>
provides built-in buffering, replacing manually implemented FIFOs.
sc_fifo<int> my_fifo(4); // FIFO with depth 4
Used in NoC simulations for packet buffering and arbitration.
✅ Example: Producer-Consumer using FIFO
SC_THREAD(producer);
SC_THREAD(consumer);
void producer() {
while (true) {
fifo.write(rand() % 100);
wait(10, SC_NS);
}
}
void consumer() {
while (true) {
int data = fifo.read();
std::cout << "Received: " << data << std::endl;
wait(10, SC_NS);
}
}
📌 Are SystemC Threads “Real Threads”? ✅ No, SystemC threads (SC_THREAD, SC_CTHREAD) are not real OS threads.
🚀 Correct Statement: “SystemC threads are not real threads but coroutines executed sequentially in a non-preemptive manner. At any given time, only one thread is running, and control is explicitly passed to the scheduler via wait().”
💡 Why? 1️⃣ SystemC threads are cooperative (not preemptive). Unlike OS threads, SystemC threads don’t run in parallel. A thread must call wait() to allow other processes to run. 2️⃣ SystemC has only one execution context. There is only one active thread at a time. The scheduler decides which process runs next based on events and time advancement.
🚀 Multiple Sensitivity Conditions in SystemC: sc_event_or_list
& sc_event_and_list
Multiple event dependencies occur frequently in computer architecture simulators.
SystemC provides event combination lists to trigger processes only when multiple events occur.
✅ Example (sc_event_or_list) – Trigger on ANY Event
SC_METHOD(myMethod);
sensitive << (event1 | event2); // Triggers if event1 OR event2 occurs
✅ Example (sc_event_and_list) – Trigger on ALL Events
SC_METHOD(myMethod);
sensitive << (event1 & event2); // Triggers ONLY IF both occur
💡 Where is this useful? NoC arbitration logic, where a router needs multiple credits before forwarding a packet.
🚀 TLM-2.0 Debug Transport Interface (dmi_allowed) DMI allows fast memory-mapped access to avoid unnecessary transactions. ✅ Example:
if (trans.is_dmi_allowed()) {
dmi_ptr = socket->get_direct_mem_ptr(trans, dmi_data);
}
🚀 Multi-Clock Synchronization Using sc_signal_rv<>
SoC routers often operate across multiple clock domains.
SystemC provides sc_signal_rv<>
for multi-clock synchronization.
✅ Example:
sc_signal_rv<4> data_bus; // 4-bit multi-clock signal
🚀 Where is this useful? CPU-GPU interconnect modeling, where clock domain crossing (CDC) requires special handling.