Every SystemC simulation has exactly one entry point: sc_main. It's the function where you build the design hierarchy, wire up ports, configure the simulation, and hand control to the kernel. If SC_MODULE is where you describe what a component does, sc_main is where you describe how everything connects and when it runs.

This article covers everything that happens in sc_main — the elaboration phase, sc_start() semantics, how to control simulation time, VCD waveform tracing, error handling with sc_report_handler, and how to inspect state after simulation ends.

1. The Role of sc_main

sc_main replaces the standard C++ main(). The SystemC library provides its own main() that calls sc_main after performing kernel initialization. You never write int main() in a SystemC project.

int sc_main(int argc, char* argv[]) {
    // 1. Elaborate  — instantiate modules, bind ports
    // 2. Configure  — set up tracing, initial signal values
    // 3. Simulate   — call sc_start()
    // 4. Analyze    — read results, check assertions
    return 0;
}

The return value works like a normal C++ return: 0 means success. Non-zero values are passed back to the calling shell, useful for CI pass/fail detection.

Why not main()? The SystemC runtime needs to initialize the simulation kernel, the time resolution, and global event queues before any user code runs. The library's own main() performs this setup, then calls your sc_main. This is why SystemC executables link against -lsystemc — the library supplies main().

2. Elaboration — The Construction Phase

Elaboration is everything that happens in sc_main before sc_start() is called. During elaboration:

sc_main begins
user code
Elaboration
constructors, port binding
sc_start()
simulation runs
returns to sc_main
post-sim analysis

Typical elaboration structure

int sc_main(int, char**) {
    // Signals (channels between modules)
    sc_signal<bool>  reset;
    sc_signal<int>   data_bus;
    sc_clock          clk("clk", sc_time(10, SC_NS));

    // Module instantiation
    CPU  cpu("cpu");
    RAM  ram("ram");
    DMA  dma("dma");

    // Port binding — connect modules to signals
    cpu.clk(clk);     cpu.reset(reset);  cpu.data(data_bus);
    ram.clk(clk);     ram.data(data_bus);
    dma.clk(clk);     dma.data(data_bus);

    // Initial conditions
    reset.write(true);

    // Start simulation
    sc_start(1000, SC_NS);

    return 0;
}
Object lifetimes matter All signals, clocks, and modules must stay alive for the entire simulation. Declare them as local variables at the top of sc_main — not inside helper functions that return before simulation ends.

3. sc_start() — Running the Simulation

sc_start() transfers control from your code to the SystemC kernel. The kernel runs the simulation until one of its stopping conditions is met, then returns control back to sc_main.

sc_start() variants

CallBehaviour
sc_start() Run until no more events are scheduled (run to completion)
sc_start(100, SC_NS) Advance simulation by exactly 100 ns, then return
sc_start(sc_time(500, SC_NS)) Same as above using an sc_time object
sc_start(SC_ZERO_TIME) Run one delta cycle (all pending evaluate-update passes at current time)

You can call sc_start() multiple times. Control returns to sc_main between calls, letting you inspect signal state, modify inputs, or conditionally decide how much more time to simulate:

// Reset for 2 clock cycles
reset.write(true);
sc_start(20, SC_NS);

// Release reset and run main test
reset.write(false);
sc_start(500, SC_NS);

// Inject a fault mid-simulation
data_bus.write(0xDEAD);
sc_start(100, SC_NS);

// Check final state
cout << "Status: " << status_reg.read() << endl;
Writing signals between sc_start() calls When you write to a signal between two sc_start() calls, the write takes effect at the start of the next sc_start() — specifically during the first delta cycle. This is correct and expected; you're writing during elaboration of the next simulation segment.

4. sc_stop() — Stopping from Inside a Process

While sc_start(N, SC_NS) stops after N nanoseconds of simulation time, sometimes you need to stop based on a condition discovered inside a process — for example, when a testbench detects a failure or the design signals completion.

void monitor_proc() {
    while (true) {
        wait(clk.posedge_event());

        if (error_flag.read()) {
            cout << "[FAIL] Error at " << sc_time_stamp() << endl;
            sc_stop();   // signals the kernel to stop after this delta
        }

        if (done_flag.read()) {
            cout << "[PASS] Simulation complete at " << sc_time_stamp() << endl;
            sc_stop();
        }
    }
}

sc_stop() does not immediately halt the simulation. It sets a flag in the kernel that causes sc_start() to return at the end of the current delta cycle. All processes currently in their evaluate phase complete normally first.

FunctionWhere calledEffect
sc_start(N, SC_NS) sc_main Advance N ns, then return to sc_main
sc_stop() Any process or sc_main Request kernel to stop; sc_start() returns at end of current delta
sc_pause() Any process or sc_main Pause simulation; can resume with another sc_start()

5. Simulation Time — sc_time_stamp() and sc_time_remaining()

Two functions let you inspect the simulation clock from anywhere — including inside processes:

// Current simulation time
sc_time now = sc_time_stamp();
cout << "Time: " << now << endl;        // prints e.g. "100 ns"
cout << now.to_double() << endl;       // double in current time unit
cout << now.to_string() << endl;       // "100 ns"

// Time remaining in current sc_start() budget
// (only valid inside an sc_start() call)
sc_time remaining = sc_time_remaining();

Time resolution

SystemC maintains a global time resolution — the smallest representable time unit. The default is 1 ps. You can change it before the first sc_start() call (and before any signal construction):

// Set resolution to 1 fs — must come before anything else in sc_main
sc_set_time_resolution(1, SC_FS);

// Default: 1 ps
// Once set (or used), resolution is locked — cannot change again
Time resolution is global and irreversible Set resolution before creating any sc_time objects or signals. The moment any sc_time is constructed, the resolution locks. Attempting to change it afterward throws a runtime error.

6. VCD Waveform Tracing

Value Change Dump (VCD) is the standard format for recording signal waveforms. GTKWave, ModelSim, and Verdi can all open VCD files. SystemC has built-in VCD support:

int sc_main(int, char**) {
    sc_clock         clk("clk",   sc_time(10, SC_NS));
    sc_signal<bool> reset;
    sc_signal<int>  data;

    MyModule dut("dut");
    dut.clk(clk);  dut.reset(reset);  dut.data(data);

    // Open VCD file — must be before sc_start()
    sc_trace_file* tf = sc_create_vcd_trace_file("sim_waves");
    tf->set_time_unit(1, SC_NS);   // waveform time unit

    // Register signals to trace
    sc_trace(tf, clk,   "clk");
    sc_trace(tf, reset, "reset");
    sc_trace(tf, data,  "data");

    // Run simulation
    reset.write(true);
    sc_start(20, SC_NS);
    reset.write(false);
    sc_start(200, SC_NS);

    // Close trace — flushes and finalizes the VCD file
    sc_close_vcd_trace_file(tf);

    return 0;
    // Output: sim_waves.vcd
}

The traced signals appear in sim_waves.vcd. Open it with gtkwave sim_waves.vcd to see the full timing diagram.

What can be traced?

Typesc_trace() works?
sc_signal<bool>, sc_clockYes — standard
sc_signal<int>, sc_signal<double>Yes
sc_signal<sc_uint<N>>, sc_lv<N>Yes
Plain C++ variables (int x)Yes — but updates only captured at signal change boundaries
Custom structsOnly if you implement the sc_trace overload for them

7. Error Handling with sc_report_handler

SystemC uses a unified reporting system for all runtime messages — warnings, errors, and fatal conditions. By default, errors print to stderr and abort the simulation. You can intercept and customize this behavior with sc_report_handler.

Default severity levels

MacroDefault action
SC_REPORT_INFO("id", "msg")Print to stdout
SC_REPORT_WARNING("id", "msg")Print warning to stderr
SC_REPORT_ERROR("id", "msg")Print error, throw sc_exception
SC_REPORT_FATAL("id", "msg")Print fatal, call abort()

Custom error handler

void my_handler(const sc_report& rep, const sc_actions& actions) {
    if (rep.get_severity() == SC_ERROR) {
        cerr << "[ERR] " << rep.get_msg() << " @ "
             << sc_time_stamp() << endl;
        sc_stop();                // stop simulation on any error
    } else {
        sc_report_handler::default_handler(rep, actions);
    }
}

int sc_main(int, char**) {
    sc_report_handler::set_handler(my_handler);
    // ... rest of sc_main
}

Setting actions per severity

// Make warnings throw an exception (fail fast in CI)
sc_report_handler::set_actions(SC_WARNING, SC_THROW);

// Silence SC_INFO messages entirely
sc_report_handler::set_actions(SC_INFO, SC_DO_NOTHING);

// Log all messages to a file
sc_report_handler::set_log_file_name("sim.log");

8. Post-Simulation Analysis

After sc_start() returns, the simulation has ended but signal objects are still alive. You can read final signal values, compute coverage metrics, and print pass/fail results:

sc_start(1000, SC_NS);

// Read final state
cout << "Final PC:     " << pc.read()     << endl;
cout << "Final status: " << status.read()  << endl;
cout << "Sim ended at: " << sc_time_stamp() << endl;

// Assert expected values
if (result.read() != EXPECTED) {
    cerr << "FAIL: expected " << EXPECTED << " got " << result.read() << endl;
    return 1;   // non-zero = CI failure
}

cout << "PASS" << endl;
return 0;

You cannot call sc_start() again after sc_stop() has been called — the simulation is finalized. For interactive or multi-run scenarios, use sc_pause() instead.


9. Command-Line Arguments

sc_main(int argc, char* argv[]) receives the same command-line arguments as a regular C++ main(). Use them to parameterize simulation runs:

int sc_main(int argc, char* argv[]) {
    int sim_ns = 1000;                    // default duration
    if (argc > 1) sim_ns = atoi(argv[1]);

    // ... build design ...
    sc_start(sim_ns, SC_NS);
    return 0;
}

// Run: ./sim 5000     → simulates 5000 ns
// Run: ./sim          → simulates 1000 ns (default)

10. Complete sc_main Template

Here is a production-ready sc_main template you can use as the starting point for any SystemC project:

#include <systemc.h>
#include "my_module.h"

int sc_main(int argc, char* argv[]) {
    // ── Time resolution (before anything else) ──────────────────
    sc_set_time_resolution(1, SC_PS);

    // ── Error handling ───────────────────────────────────────────
    sc_report_handler::set_actions(SC_ERROR,   SC_DISPLAY | SC_STOP);
    sc_report_handler::set_actions(SC_WARNING,  SC_DISPLAY);

    // ── Clocks and signals ───────────────────────────────────────
    sc_clock          clk  ("clk",   sc_time(10, SC_NS));
    sc_signal<bool>  reset;
    sc_signal<int>   data_out;

    // ── DUT instantiation + port binding ─────────────────────────
    MyModule dut("dut");
    dut.clk(clk);
    dut.reset(reset);
    dut.data_out(data_out);

    // ── VCD tracing ──────────────────────────────────────────────
    sc_trace_file* tf = sc_create_vcd_trace_file("waves");
    sc_trace(tf, clk,      "clk");
    sc_trace(tf, reset,    "reset");
    sc_trace(tf, data_out, "data_out");

    // ── Simulation sequence ──────────────────────────────────────
    reset.write(true);
    sc_start(20, SC_NS);          // hold reset 2 cycles

    reset.write(false);
    sc_start(980, SC_NS);         // main test run

    // ── Post-sim analysis ────────────────────────────────────────
    sc_close_vcd_trace_file(tf);

    bool pass = (data_out.read() == 42);
    cout << (pass ? "PASS" : "FAIL")
         << " @ " << sc_time_stamp() << endl;

    return pass ? 0 : 1;
}

Summary


📬 Get new articles in your inbox

Deep dives on SystemC, C++, and embedded systems — no spam, unsubscribe any time.

No spam, unsubscribe any time. Privacy Policy

Aditya Gaurav

Aditya Gaurav

Embedded systems engineer specializing in SystemC, ARM architecture, and C/C++ internals. Writing deep technical dives for VLSI and embedded engineers.

Part of the SystemC Foundations guide Browse all SystemC guides →