Chapter 7: SystemC 1666-2023 LRM

LRM Bridge: Predefined Channels

sc_signal, sc_buffer, resolved signals, sc_clock, sc_fifo, sc_mutex, and sc_semaphore as standard communication building blocks.

The IEEE 1666 LRM provides a set of predefined primitive and hierarchical channels that form the standard toolbox for modeling hardware communication.

End-to-End Predefined Channels Example

This complete example demonstrates how sc_signal, sc_clock, sc_fifo, and sc_mutex interoperate to model a multi-producer, clocked FIFO system.

#include <systemc>
 
SC_MODULE(HardwareSystem) {
    // 1. Clock and Signals
    sc_core::sc_in<bool> clk;
    sc_core::sc_signal<bool> data_ready{"data_ready"};
 
    // 2. FIFO (Hierarchical Channel)
    // A thread-safe queue holding up to 4 integers
    sc_core::sc_fifo<int> data_fifo{"data_fifo", 4};
 
    // 3. Mutex (Primitive Channel)
    // Ensures only one producer accesses the debug port at a time
    sc_core::sc_mutex bus_mutex{"bus_mutex"};
 
    SC_CTOR(HardwareSystem) {
        SC_THREAD(producer_a);
        sensitive << clk.pos();
 
        SC_THREAD(producer_b);
        sensitive << clk.pos();
 
        SC_THREAD(consumer);
        sensitive << clk.pos();
    }
 
    void producer_a() {
        wait(2, sc_core::SC_NS); // Wait for initialization
        while(true) {
            wait(); // Wait for clock
            
            // Lock the mutex before printing to the shared bus/console
            bus_mutex.lock();
            std::cout << "@" << sc_core::sc_time_stamp() << " [Prod A] Acquired bus lock." << std::endl;
            
            // Blocking write. If FIFO is full, thread suspends here until space is available.
            data_fifo.write(0xA);
            data_ready.write(true); // Signal an update (Delta Cycle delay)
            
            bus_mutex.unlock();
            wait(20, sc_core::SC_NS); // Work delay
        }
    }
 
    void producer_b() {
        wait(5, sc_core::SC_NS);
        while(true) {
            wait();
            
            bus_mutex.lock();
            std::cout << "@" << sc_core::sc_time_stamp() << " [Prod B] Acquired bus lock." << std::endl;
            
            // Non-blocking write. If full, it fails gracefully.
            if (data_fifo.nb_write(0xB)) {
                data_ready.write(true);
            }
            
            bus_mutex.unlock();
            wait(15, sc_core::SC_NS);
        }
    }
 
    void consumer() {
        while(true) {
            wait();
            // Blocking read. If FIFO is empty, thread suspends here.
            int val = data_fifo.read(); 
            std::cout << "@" << sc_core::sc_time_stamp() << " [Consumer] Read value: 0x" 
                      << std::hex << val << std::endl;
            data_ready.write(false);
        }
    }
};
 
int sc_main(int argc, char* argv[]) {
    sc_core::sc_clock clk("clk", 10, sc_core::SC_NS);
    HardwareSystem sys("sys");
    sys.clk(clk);
 
    sc_core::sc_start(100, sc_core::SC_NS);
    return 0;
}

Channel Semantics

sc_signal and sc_buffer

sc_signal<T> applies written values during the update phase. If the new value equals the old value, no event is triggered. sc_buffer<T> is identical to sc_signal, but it triggers a value-changed event every time it is written to, regardless of whether the value actually changed. Use sc_buffer when the act of writing is semantically important.

Resolved Signals (sc_rv / sc_logic)

SystemC provides resolved signals for multi-writer logic modeling (e.g., tri-state buses, wired-OR). Use sc_core::sc_out_resolved to model true multi-driver electrical contention.

sc_clock

sc_clock provides periodic boolean-like toggling behavior. It provides strict timing stability and guarantees zero-skew edge events across the entire design.

sc_fifo

sc_fifo<T> models queued producer-consumer communication safely across process boundaries. It natively supports both blocking (read(), write()) and non-blocking (nb_read(), nb_write()) access, and provides events to wait on when it becomes full or empty.

sc_mutex and sc_semaphore

Mutexes and semaphores control shared resource access at an abstract level. A process that fails to lock a mutex will suspend and be automatically resumed by the scheduler when the mutex is unlocked.

Comments and Corrections