FIFOs, Mutexes, Semaphores, and Custom Channels
When to use built-in channels and how to design communication abstractions of your own.
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.
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.
Comments and Corrections