Chapter 13: Modeling Best Practices

Modeling Best Practices: Debugging Playbook

How experts debug SystemC failures by separating elaboration, scheduling, binding, timing, payload, and configuration problems.

Modeling Best Practices: Debugging Playbook

SystemC failures often look mysterious because C++ execution, kernel scheduling, and modeled hardware behavior are heavily interleaved. A segmentation fault could be a C++ pointer error, or it could be a TLM payload mismatch. A simulation hang could be an infinite loop, or it could be a delta-cycle deadlock.

Expert debugging requires classifying the failure first, then applying the correct tools.

Class 1: Elaboration and Binding Failures

Symptoms: Exceptions thrown before sc_start() begins; messages about unbound ports. Cause: The LRM requires all ports and exports to be bound before simulation starts. Playbook:

  • Use the LRM API sc_core::sc_get_top_level_objects() to print the hierarchy.
  • Ensure no modules were instantiated as local stack variables inside constructors.
  • Check that multi-ports (sc_port<IF, N>) have the correct number of bindings.

Class 2: Scheduling Failures (Hangs and Deadlocks)

Symptoms: Simulation time (sc_time_stamp()) stops advancing, but CPU usage is 100%. Or, simulation freezes entirely. Cause:

  • CPU 100%: A delta-cycle loop. Process A triggers B, B triggers A, all in SC_ZERO_TIME.
  • Freeze: An SC_THREAD forgot to call wait(), locking the cooperative scheduler forever. Playbook:
  • Add SC_REPORT_INFO inside suspected threads.
  • If it's a delta loop, use the LRM API sc_core::sc_delta_count() to print the delta cycle count. If it increases while time stands still, you found the loop.

Class 3: TLM-2.0 Protocol Failures

Symptoms: Firmware reads garbage data, routers forward to the wrong target, or an initiator throws an TLM_INCOMPLETE_RESPONSE error. Cause: Violation of the TLM-2.0 base protocol. Playbook:

  • Print the payload fields (Address, Data Length, Command, Byte Enables) in the target before processing.
  • Verify the Target modifies set_response_status().
  • If using DMI (Direct Memory Interface), disable it temporarily. If the bug disappears, your DMI invalidation logic is flawed.

Complete Example: Debugging a Delta Cycle Loop

This complete sc_main demonstrates a classic delta-cycle loop (Class 2 failure) and how to use the LRM APIs (sc_delta_count) to detect and debug it programmatically.

#include <systemc>
#include <iostream>
 
SC_MODULE(DeltaLoopDemo) {
    sc_core::sc_signal<bool> sig_a{"sig_a"};
    sc_core::sc_signal<bool> sig_b{"sig_b"};
 
    SC_CTOR(DeltaLoopDemo) {
        SC_METHOD(process_a);
        sensitive << sig_b; // A reacts to B
        
        SC_METHOD(process_b);
        sensitive << sig_a; // B reacts to A
 
        SC_METHOD(monitor_deltas);
        sensitive << sig_a << sig_b;
    }
 
    void process_a() {
        // Invert B and write to A
        sig_a.write(!sig_b.read());
    }
 
    void process_b() {
        // Invert A and write to B
        sig_b.write(!sig_a.read());
    }
 
    void monitor_deltas() {
        // The LRM provides sc_delta_count() to track evaluation phases
        uint64_t current_delta = sc_core::sc_delta_count();
        std::cout << "[Time: " << sc_core::sc_time_stamp() 
                  << "] Delta Count: " << current_delta << "\n";
 
        // Safeguard to prevent an actual infinite loop in this demonstration
        if (current_delta > 10) {
            SC_REPORT_ERROR("DEBUG", "Delta cycle loop detected! Aborting.");
        }
    }
};
 
int sc_main(int argc, char* argv[]) {
    // Configure the report handler to stop instead of abort for the demo
    sc_core::sc_report_handler::set_actions(sc_core::SC_ERROR, sc_core::SC_DISPLAY | sc_core::SC_STOP);
 
    DeltaLoopDemo demo("demo");
 
    std::cout << "Starting Simulation... Watch the delta count explode.\n";
    
    // Kickoff the loop
    demo.sig_b.write(true); 
 
    sc_core::sc_start(1, sc_core::SC_MS);
    
    std::cout << "Simulation stopped cleanly after detecting the loop.\n";
    return 0;
}

Explanation of the Execution

When you run this code, process_a writes to sig_a. In the update phase, sig_a changes, triggering process_b. process_b writes to sig_b. In the next update phase, sig_b changes, triggering process_a.

Because signal updates take zero simulation time, sc_time_stamp() remains at 0 s, but the kernel evaluates continuously.

The output will look like this:

Starting Simulation... Watch the delta count explode.
[Time: 0 s] Delta Count: 1
[Time: 0 s] Delta Count: 2
[Time: 0 s] Delta Count: 3
...
[Time: 0 s] Delta Count: 11
Error: (E0000) DEBUG: Delta cycle loop detected! Aborting.
Simulation stopped cleanly after detecting the loop.

By printing sc_time_stamp() alongside sc_delta_count(), the architect instantly diagnoses a combinatorial feedback loop rather than wondering why the simulation froze.

Comments and Corrections