Chapter 3: Communication

FIFOs, Mutexes, Semaphores, and Custom Channels

When to use built-in channels and how to design communication abstractions of your own.

Listen to this lessonAudiobook mode

How to Read This Lesson

Think of this chapter as wiring discipline. Ports, exports, interfaces, and channels are not decorative; they are how the model states its contract before time starts moving.

Not every connection should be a signal. SystemC includes higher-level channels such as FIFOs, mutexes, and semaphores because many system models care more about transactions and resources than individual wires.

Standard and source context

The standard contract lives in Docs/LRMs/SystemC_LRM_1666-2023.pdf around interfaces, ports, exports, primitive channels, hierarchical channels, and predefined channels. The implementation trail is Accellera SystemC GitHub repository: sc_port, sc_export, sc_interface, sc_prim_channel, sc_signal, sc_fifo, and the writer policy helpers.

sc_fifo

sc_fifo<T> models queued communication:

#include <systemc>
using namespace sc_core;
 
SC_MODULE(Producer) {
  sc_fifo_out<int> out{"out"};
 
  SC_CTOR(Producer) {
    SC_THREAD(run);
  }
 
  void run() {
    for (int i = 0; i != 8; ++i) {
      out.write(i);
      std::cout << "Wrote " << i << " at " << sc_time_stamp() << std::endl;
      wait(10, SC_NS);
    }
  }
};
 
SC_MODULE(Consumer) {
  sc_fifo_in<int> in{"in"};
  SC_CTOR(Consumer) { SC_THREAD(run); }
  void run() {
    while (true) {
      int val = in.read();
      std::cout << "Read " << val << " at " << sc_time_stamp() << std::endl;
    }
  }
};
 
int sc_main(int, char*[]) {
  sc_fifo<int> fifo(4);
  Producer p("p");
  Consumer c("c");
  p.out(fifo);
  c.in(fifo);
  sc_start(100, SC_NS);
  return 0;
}

The blocking write() waits when the FIFO is full. The blocking read() waits when the FIFO is empty. This is often perfect for modeling pipelines, queues, and producer-consumer systems.

Mutexes and Semaphores

sc_mutex and sc_semaphore model shared resources. Use them when the model question is about arbitration or resource ownership.

#include <systemc>
using namespace sc_core;
 
SC_MODULE(Master) {
  sc_port<sc_mutex_if> bus_lock{"bus_lock"};
 
  SC_CTOR(Master) { SC_THREAD(run); }
 
  void run() {
    bus_lock->lock();
    std::cout << name() << " got lock at " << sc_time_stamp() << std::endl;
    wait(20, SC_NS);
    bus_lock->unlock();
  }
};
 
int sc_main(int, char*[]) {
  sc_mutex mutex("mutex");
  Master m1("m1"), m2("m2");
  m1.bus_lock(mutex);
  m2.bus_lock(mutex);
  sc_start(50, SC_NS);
  return 0;
}

These are modeling constructs, not magic performance tools. Use them when they express the system behavior clearly.

Custom Channels

Custom channels are where SystemC becomes a modeling framework rather than a fixed simulator. You define an interface, implement it in a channel, and bind modules to that interface.

Good custom channels hide policy:

  • timing
  • arbitration
  • buffering
  • tracing
  • protocol conversion
  • statistics

The module using the channel should care about the operation it needs, not the machinery behind it.

Design Rule

If the connection is a wire, use a signal. If the connection is a transaction, use an interface or TLM socket. If the connection is a queue, use a FIFO. If the connection is a shared resource, use a mutex or semaphore. The model reads better when the channel matches the concept.

Under the Hood: sc_fifo and sc_mutex Blocking Semantics

sc_fifo<T> and sc_mutex implement blocking synchronization using sc_event and wait(). In sysc/communication/sc_fifo.cpp, if a thread calls read() but the FIFO is empty, the channel calls wait(m_data_written_event). The thread is suspended. When another thread calls write(), it executes m_data_written_event.notify(SC_ZERO_TIME). The blocked reader is added to the runnable queue and resumes in the next delta cycle. Similarly, sc_mutex::lock() checks if the mutex is available. If not, it waits on m_free_event. When locked, the mutex stores the process handle (sc_get_current_process_handle()) of the owner to ensure only the owner can unlock() it, throwing an error if a different process attempts to release the lock.

Lesson self-check

Can you answer these clearly?

Keep moving when you can answer each question without looking back at the lesson.

Comments and Corrections