-
Notifications
You must be signed in to change notification settings - Fork 4
SingleSource library
SingleSource library consists of communication channels which implements SC interfaces. There are nine main channels in the library.

Target and Initiator are intended to connect two SC modules with 1:1 connection. Multi-target and Multi-initiator modules provides 1:N connection to connect multiple SC modules. FIFO is intended to connect two processes in the same module or to serve as a buffer for one process.
On-die port represents any external port to connect SC design to other IPs or fabric. Memory represents any kinds of on-chip SRAM, RF or ROM memory. Register is used to add state for METHOD process. The common use cases of the modules are given in the picture below.

The Single Source modules work in two modes: cycle accurate (RTL) and approximate time (TLM). Cycle accurate RTL mode intended for hardware synthesis. In RTL mode the modules provide cycle accurate simulation. Approximate time TLM (Transaction Level Modelling) mode provide fast simulation, intended for virtual prototyping. In TLM mode the modules provide approximate time simulation, there is no clock. Simulation is request-driven, executed in ordered delta-cycles (DC).
- sct_common.h -- includes of all library headers and adds using namespace
sct - sct_ipc_if.h -- interfaces, general template types and defines
- sct_initiator.h -- initiator module
- sct_target.h -- target and combinational target modules
- sct_multi_initiator.h -- initiator to connect multiple targets
- sct_multi_target.h -- target to connect multiple initiators
- sct_prim_signal.h -- primitive channel signal implementation with multiple drivers support
- sct_signal.h -- signal implementation
- sct_ports.h -- input and output ports, sc_port for target and initiator
- sct_fifo.h -- FIFO module
- sct_prim_fifo.h -- primitive channel FIFO implementation, used as base channel in TLM mode
- sct_register.h -- register to store METHOD state
- sct_clock.h -- clock with enable/disable
- sct_clk_gate_cell.h -- clock gate and clock gate signal
- sct_ff_sync_cell.h -- Flip-Flop synchronizer
- sct_ff_sync_cell.sv -- Flip-Flop synchronizer RTL implementation
SCT_TLM_MODE could be provided as compile definition: if SCT_TLM_MODE defined TLM mode is used, RTL mode is used otherwise.
There are multiple options for clock/reset levels:
-
SCT_CMN_TRAITS-- clock edge and reset level, one of six following options: -
SCT_POSEDGE_NEGRESET-- positive clock edge, negative reset level -
SCT_POSEDGE_POSRESET-- positive clock edge, positive reset level -
SCT_NEGEDGE_NEGRESET-- negative clock edge, negative reset level -
SCT_NEGEDGE_POSRESET-- negative clock edge, positive reset level -
SCT_BOTHEDGE_NEGRESET-- both clock edges, negative reset level -
SCT_BOTHEDGE_POSRESET-- both clock edges, positive reset level
Usually, positive clock edge and negative reset level are used. That is provided by define SCT_CMN_TRAITS:
#ifndef SCT_CMN_TRAITS
#define SCT_CMN_TRAITS SCT_POSEDGE_NEGRESET
#endifIf other clock edge/reset levels required, SCT_CMN_TRAITS value should be provided as compile definition.
There is an CMakeLists.txt example where sct_def_traits target has definitions for TLM mode, negative clock edge and positive reset level:
add_executable(sct_def_traits sc_main.cpp)
target_compile_definitions(sct_def_traits PUBLIC -DSCT_TLM_MODE)
target_compile_definitions(sct_def_traits PUBLIC -DSCT_CMN_TRAITS=SCT_NEGEDGE_POSRESET)The interfaces contain non-blocking functions except b_put and b_get which are may-blocking.
| Interface | Functions | Comment |
|---|---|---|
| sct_put_if | bool ready() |
Return true if it is ready to put request |
void reset_put() |
Reset this initiator/FIFO | |
void clear_put() |
Clear (remove) request put in this cycle | |
bool put(const T& data) |
Non-blocking put request into initiator/FIFO if it is ready, return ready to request | |
bool put(const T& data, sc_uint<N> mask) |
Non-blocking put request into initiator/FIFO if it is ready, mask used to enable/disable put or choose targets in multi-cast put, return ready to request |
|
void b_put(const T& data) |
May-blocking put request, could be used in THREAD process only | |
void addTo(sc_sensitive& s) |
Add put related signals to process sensitivity | |
void addTo(sc_sensitive* s, sc_process_handle* p) |
Add put related signals to process sensitivity | |
| sct_get_if | bool request() |
Return true if it has request to get |
void reset_get() |
Reset this target/FIFO | |
void clear_get() |
Clear (return back) request got in this cycle | |
T peek() |
Peek request, return current request data, if no request last data returned | |
T get() |
Non-blocking get request and remove it from FIFO/target, return current request data, if no request last data returned | |
bool get(T& data, bool enable) |
Non-blocking get request and remove it from FIFO/target if enable is true, return true if there is a request and enable is true |
|
T b_get() |
May-blocking get request, could be used in THREAD process only | |
void addTo(sc_sensitive& s) |
Add get related signals to process sensitivity | |
void addTo(sc_sensitive* s, sc_process_handle* p) |
Add get related signals to process sensitivity | |
void addPeekTo(sc_sensitive& s) |
Add peek related signal to process sensitivity | |
| sct_fifo_if | inherits sct_put_if<T> and sct_get_if<T>
|
|
unsigned size() |
FIFO size | |
unsigned elem_num() |
Number of elements in FIFO, value updated last clock edge for METHOD, last DC for THREAD | |
bool almost_full(const unsigned& N) |
Return true if FIFO has (LENGTH-N) elements or more, value updated last clock edge for METHOD, last DC for THREAD | |
void clk_nrst(sc_in<bool>& clk_in, sc_in<bool>& nrst_in) |
Bind clock and reset to FIFO | |
void addTo(sc_sensitive& s) |
Add put and get related signal to process sensitivity | |
void addToPut(sc_sensitive& s) |
Add put related signals to process sensitivity | |
void addToGet(sc_sensitive& s) |
Add get related signals to process sensitivity | |
| sct_in_if | const T& read() |
Read from signal/register |
void addTo(sc_sensitive* s, sc_process_handle* p) |
Add signals to process sensitivity | |
| sct_inout_if | const T& read() |
Read from signal/register |
void write(const T& val) |
Write to signal/register | |
void addTo(sc_sensitive* s, sc_process_handle* p) |
Add signals to process sensitivity |
Functions addTo, addToPut, addToGet and addPeekTo are used to add the channel to process sensitivity list. For target and initiator instead addTo operator << can be used. For FIFO instead addToPut and addToGet operator << fifo.PUT, << fifo.GET, and << fifo.PEEK can be used.
SystemC design with single source library can use method and thread processes created with SC_METHOD and SC_THREAD correspondently. It is recommended to use SCT_METHOD and SCT_THREAD macros instead them. Clocked thread process created with SC_CTHREAD normally not used.
All the channels used in a thread process should use the same clock and edge as the process sensitive. A channel could have different reset or reset level than thread process. If all the channels have different reset or reset level, process reset is specified with third parameter of SCT_THREAD macro.
If a thread process has reset signal, it should have the reset specification with async_reset_signal_is or/and sync_reset_signal_is. A thread process should be sensitive to all single source channels accessed in its function code. If process is sensitive only to signal and input/output ports, the clock is provided with second parameter of SCT_THREAD macro.
Method process should be sensitive to all single source channels accessed in its function code. Method should not be sensitive to reset, as its provided implicitly through the channels used.
template <class T>
class MyModule : public sc_module {
sc_in<bool> clk{"clk"};
sct_target<T> targ{"targ"};
sct_initiator<T> init{"init"};
sct_signal<T> s{"s"};
explicit MyModule(const sc_module_name& name) : sc_module(name) {
SC_THREAD(thrdProc1);
sensitive << targ; // Clock and reset taken from targ
async_reset_signal_is(nrst, 0); // Reset specification required
SCT_THREAD(thrdProc2, clk, nrst); // Clock and reset explicitly provided
sensitive << s;
async_reset_signal_is(nrst, 0); // Reset specification required
SC_METHOD(methProc);
sensitive << init; // No reset in sensitivity
}
};
If any process sensitive to a channel which is not read inside or not sensitive to a channel which is read inside, error reported by ICSC. The error is reported for single channels and for vector/array of channels, no individual channels in vector/array are considered here.
Target and Initiator are channels intended to connect two user defined modules. Initiator implements sct_put_if interface and could be used in one METHOD or THREAD process to put requests. Target implements sct_get_if interface and could be used in one METHOD or THREAD process to get requests which put by the connected Initiator.
To connect two modules, Target placed in one modules, Initiator in another one. Target and Initiator should be connected to clock and reset with clk_nrst() function. Target and Initiator are connected to each other with method bind(), called in their common parent module constructor. Both Target and Initiator have method bind(), any of them can be called.

struct Producer : public sc_module {
sc_in<bool> clk{"clk"};
sc_in<bool> nrst{"nrst"};
sct_initiator<T> init{"init"};
explicit Producer (const sc_module_name& name) : sc_module(name) {
init.clk_nrst(clk, nrst);
}
}
struct Consumer : public sc_module {
sc_in<bool> clk{"clk"};
sc_in<bool> nrst{"nrst"};
sct_target<T> targ{"targ"};
explicit Consumer (const sc_module_name& name) : sc_module(name) {
targ.clk_nrst(clk, nrst);
}
}
struct Top: public sc_module {
Producer prod{"prod"};
Consumer cons{"cons"};
explicit Top(const sc_module_name& name) : sc_module(name) {
prod.clk(clk); prod.nrst(nrst);
cons.clk(clk); cons.nrst(nrst);
// Call bind() method of initiator or bind() method of target
prod.init.bind(cons.targ);
}
}Target and Initiator have the same template parameters:
template<
class T, // Payload data type
class TRAITS = SCT_CMN_TRAITS, // Clock edge and reset level traits
bool TLM_MODE = SCT_CMN_TLM_MODE> // RTL (0) or TLM (1) mode
class sct_initiator {};
template<
class T, // Payload data type
class TRAITS = SCT_CMN_TRAITS, // Clock edge and reset level traits
bool TLM_MODE = SCT_CMN_TLM_MODE> // RTL (0) or TLM (1) mode
class sct_target {};Target and Initiator constructor parameters:
sct_target(const sc_module_name& name, // Module name -- same as instance variable name
bool sync_ = 0, // Is register required to pipeline request
bool always_ready_ = 0); // Is always ready to get request
sct_initiator(const sc_module_name& name, // Module name -- same as instance variable name
bool sync_ = 0); // Is register required to pipeline request That is enough to setsync_ = 1 for Target or for Initiator to have register added.
Target and initiator can be used in SystemC method process. The method process should be created with SC_METHOD or SCT_METHOD macro in the module constructor. The method process should have sensitivity list with all the targets/initiators accessed in the process function.
// Initiator and target in method process example
struct Producer : public sc_module {
sct_initiator<T> init{"init"};
explicit Producer (const sc_module_name& name) : sc_module(name) {
SC_METHOD(initProc);
sensitive << init;
}
void initProc {
// Put data into init
}
}
struct Consumer : public sc_module {
sct_target<T> targ{"targ"};
explicit Consumer (const sc_module_name& name) : sc_module(name) {
SC_METHOD(targProc);
sensitive << targ;
}
void targProc{
// Get data from targ
}
}Target and initiator can be used in clocked thread process. Clocked thread process should be created with SC_THREAD or SCT_THREAD macro, but not with SC_CTHREAD. The thread process should have sensitivity list with all the targets/initiators accessed in the process function as for method process. If the thread process has reset signal, it should have the reset specification with async_reset_signal_is or/and sync_reset_signal_is.
// Initiator and target in thread process example
struct Producer : public sc_module {
sct_initiator<T> init{"init"};
explicit Producer (const sc_module_name& name) : sc_module(name) {
SC_THREAD(initProc);
sensitive << init;
async_reset_signal_is(nrst, 0);
}
void initProc {
// Reset init to set default values
wait();
while(true) {
// Put data into init
wait();
}
}
}
struct Consumer : public sc_module {
sct_target<T> targ{"targ"};
explicit Consumer (const sc_module_name& name) : sc_module(name) {
SC_THREAD(targProc);
sensitive << targ;
async_reset_signal_is(nrst, 0);
}
void targProc{
// Reset init to set default values
wait();
while(true) {
// Get data from targ
wait();
}
}
}There are three kinds of connections which could be organized:
- Combinational,
- Buffered,
- Buffered with FIFO.
In combinational connection request part of connection contains core_req and core_data signals, which could be used directly or through the pipelining register (specified with second parameter of Target/Initiator constructor). There is no back-pressure signal, so Target process should be always ready to get request. Initiator process does not need to check ready to put request (method ready() always returns true).

Combinational connection is provided with last parameter of sct_target<> constructor or with using special target class sct_comb_target<>.
In combinational connection put data into Initiator can be done without checking if the Initiator is ready.
// Initiator and always ready target in method process example
struct Producer : public sc_module {
sct_initiator<T> init{"init"};
explicit Producer (const sc_module_name& name) : sc_module(name) {
SC_METHOD(initProc); sensitive << init;
}
void initProc {
T val = getSomeValue(); // Put at every path, reset is not required
init.put(val); // Do not check ready() as connected Target is always ready
}
}
struct Consumer : public sc_module {
// Combinational target
sct_comb_target<T> targ{"targ"};
explicit Consumer (const sc_module_name& name) : sc_module(name) {
SC_METHOD(targProc); sensitive << targ;
}
void targProc{
T val;
if (targ.get(val)) { // Get at every path, reset is not required
doSomething(val);
}
}
}In thread process it needs to reset Initiator and Target in the reset section.
// Initiator and always ready target in thread process example
struct Producer : public sc_module {
sct_initiator<T> init{"init"};
explicit Producer (const sc_module_name& name) : sc_module(name) {
SC_THREAD(initProc); sensitive << init;
async_reset_signal_is(nrst, 0);
}
void initProc {
init.reset_put(); // Reset is required in thread process
wait();
while(true) {
T val = getSomeValue(); // Put every cycle
init.put(val); // Do not check ready() as connected Target is always ready
wait();
}
}
}
struct Consumer : public sc_module {
sct_comb_target<T> targ{"targ"};
explicit Consumer (const sc_module_name& name) : sc_module(name) {
SC_THREAD(targProc); sensitive << targ;
async_reset_signal_is(nrst, 0);
}
void targProc{
targ.reset_get(); // Reset is required in thread process
wait();
while(true) {
if (targ.request()) {
doSomething(targ.get());
}
wait();
}
}
}Using Target and Initiator in method and thread process looks very similar. In the next sections examples using method and thread process will be mixed.
In buffered connection core_ready signal is used as backpressure when Target is not ready to get request. This connection called buffered as it has the buffer register inside Target or Initiator to store one request if Target is not ready. THis kind of connection is the most common and used as default one.
Request part of the connection contains core_req and core_data signals, which could be used directly or through the pipelining register (specified with second parameter of Target/Initiator constructor). The pipelining register is additional to the buffer register. Response part contains core_ready signal which is passed through register to avoid combinational loop. If target process is method this register explicitly added, if it is thread this register is implicitly provided by the process.

struct Producer : public sc_module {
sct_initiator<T> init{"init"};
explicit Producer (const sc_module_name& name) : sc_module(name) {
SC_METHOD(initProc); sensitive << init;
}
void initProc {
init.reset_put(); // Reset required as put is done at some path only
if (init.ready()) { // Check ready required as target could be not ready
init.put(getSomeValue());
}
}
}
struct Consumer : public sc_module {
sct_target<T> targ{"targ"};
explicit Consumer (const sc_module_name& name) : sc_module(name) {
SC_THREAD(targProc); sensitive << targ;
async_reset_signal_is(nrst, 0);
}
void targProc {
targ.reset_get();
wait();
while(true) {
if (targ.request()) {
doSomething(targ.get());
}
wait();
}
}
}The buffered connection with FIFO provides additional buffer to store requests until their processed by the target process.
FIFO can be added to Target with add_fifo() method:
template<unsigned LENGTH> // FIFO size (maximal number of elements)
void add_fifo(bool sync_valid = 0, // Is register required to pipeline core_req and core_data
bool sync_ready = 0, // Is register required to pipeline core_ready
bool init_buffer = 0); // Initialize all the elements with zeros
// First element to get is always initialized to zero 
template<class T>
struct A : public sc_module {
sct_target<T> run{"run"};
explicit A(const sc_module_name& name) : sc_module(name) {
run.clk_nrst(clk, nrst);
run.template add_fifo<2>(1, 1); // Add FIFO with 2 element and registers in request/response
}
}The discussed protocol considers buffered connection w/o FIFO. Request is taken by Target when core_req and core_ready both are high. Target can return it to the target process immediately or store the request in the buffer. Initiator sets new request when the previous one has been taken.
The first diagram below represents Target and Initiators accessed in thread processes. The second diagram represents Target and Initiators accessed in method processes.


Signal can be used for inter-process communication between processes in the same module. For communication between processes in different modules input/output ports are used together with signal.

Signal and output port implement sct_inout_if, and can be written by one process. Signal, input and output ports implement sct_in_if, and can be read by one or mode processes.
template<
class T, bool TLM_MODE = SCT_CMN_TLM_MODE>
class sct_signal {};
template<
class T, bool TLM_MODE = SCT_CMN_TLM_MODE>
class sct_in {};
template<
class T, bool TLM_MODE = SCT_CMN_TLM_MODE>
class sct_out {};Using signal and input/output ports in thread process requires to have clock/reset for these channels which provided with SCT_THREAD macro:
SCT_THREAD(proc, clk, rst); /// Used if the process sensitive to signals/ports only
SCT_THREAD(proc, clk); /// Used if the process sensitive to signals/ports and other channelsIn this example sigThread sensitive to signals only:
sct_signal<T> s{"s"};
MyModule(const sc_module_name& name) : sc_module(name) {
SCT_THREAD(sigThread, clk, nrst); // Clock edge/reset level taken from SCT_CMN_TRAITS
sensitive << s; // Only signal `s` is read inside the process
async_reset_signal_is(nrst, 0);
}sc_vector of sct_signal, sct_in and sct_out supported. Binding of while vector to another vector is supported.
class A : public sc_module {
sc_vector<sct_out<T>> resp{"resp", 3};
};
class Top {
A a{"a"};
sc_vector<sct_signal<T>> resp{"resp", 3};
Top (const sc_module_name& name) : sc_module(name) {
a.resp(resp); // All vector elements bound
}
}In RTL mode sct_signal is based on sc_signal, sct_in/sct_out are based on sc_in/sc_out.
The FIFO can be used for inter-process communication between processes in the same module and for storing requests inside one process. Also the FIFO could be used inside of Target as an extended buffer.

The FIFO implements sct_fifo_if. FIFO has size template parameter which is a positive number.
template<
class T,
unsigned LENGTH, // Size (maximal number of elements)
class TRAITS = SCT_CMN_TRAITS, // Clock edge and reset level traits
bool TLM_MODE = SCT_CMN_TLM_MODE> // RTL (0) or TLM (1) mode
>
class sct_fifo {};The FIFO can have combinational or registered request (core_req and core_data) and response (core_ready) kind which specified in constructor parameters.
sct_fifo(const sc_module_name& name,
bool sync_valid = 0, // Request path has synchronous register
bool sync_ready = 0, // Response path has synchronous register
bool use_elem_num = 0, // Element number/Almost full or empty used
bool init_buffer = 0) // Initialize all buffer elements with zeros in reset
// First element to get is always initialized to zero Minimal FIFO size to provide full throughput depends on process types and request/response kind. In the table below minimal required FIFO sizes to provide full throughput are given.
| Initiator process | Target process | sync_valid | sync_ready | Minimal FIFO size |
|---|---|---|---|---|
| method | method | 0 | 0 | 1 |
| method | method | 0 | 1 | 1 |
| method | method | 1 | 0 | 1 |
| method | method | 1 | 1 | 2 |
| method | thread | 0 | 0 | 1 |
| method | thread | 0 | 1 | 2 |
| method | thread | 1 | 0 | 2 |
| method | thread | 1 | 1 | 3 |
| thread | method | 0 | 0 | 1 |
| thread | method | 0 | 1 | 2 |
| thread | method | 1 | 0 | 2 |
| thread | method | 1 | 1 | 3 |
| thread | thread | 0 | 0 | 2 |
| thread | thread | 0 | 1 | 3 |
| thread | thread | 1 | 0 | 3 |
| thread | thread | 1 | 1 | 4 |
FIFO could be used for processes communication instead of set of signals. FIFO has only one writer and one reader process, in comparison with sct_signal which could be read in multiple processes. For 1:N communication array or sc_vector of FIFOs could be used.
struct Top : public sc_module {
sct_fifo<T, 2> fifo{"fifo", 1}; // Pipelining register for request
explicit Top(const sc_module_name& name) : sc_module(name) {
fifo.clk_nrst(clk, nrst);
SC_THREAD(producerProc); sensitive << fifo.PUT; // Process puts to FIFO
async_reset_signal_is(nrst, 0);
SC_METHOD(consumerProc); sensitive << fifo.GET; // Process gets from FIFO
}
}
void producerProc() {
fifo.reset_put();
wait();
while (true) {
if (fifo.ready()) { // If FIFO is ready put next value
fifo.put(getSomeVal());
}
wait();
}
}
void consumerProc() {
fifo.reset_get();
T val;
if (fifo.get(val)) {
doSomething(val);
}
}struct Top : public sc_module {
sc_in<bool> clk{"clk"};
sc_in<bool> nrst{"nrst"};
sct_fifo<T, 5> fifo{"fifo"};
explicit Top(const sc_module_name& name) : sc_module(name) {
fifo.clk_nrst(clk, nrst);
SC_THREAD(storeProc); sensitive << fifo; // Process puts and gets to FIFO
async_reset_signal_is(nrst, 0);
}
}
void storeProc() {
fifo.reset();
wait();
while (true) {
if (fifo.ready()) {
fifo.put(getSomeValue());
}
wait();
if (fifo.request()) {
doSomething(fifo.get());
}
}
}
Register is used to add state for METHOD process. Register is written in one method process and could be read in the same or other method process(es). Register is normally added to sensitivity list of process where it is read. Register can be read in thread process.
Register has the same template parameters as Target/Initiator:
template<
class T, // Payload data type
class TRAITS = SCT_CMN_TRAITS, // Clock edge and reset level traits
bool TLM_MODE = SCT_CMN_TLM_MODE> // RTL (0) or TLM (1) mode
class sct_register {};Register has the following methods:
// Reset register, set it value to stored at last clock edge
void reset();
// Write new value to register
void write(const T& data);
// Read value stored at last clock edge
T read();
// To skip using read()
operator T ();Register can initiate a new request. That means an output request can depend on register state.
sct_target<T> targ{"targ"};
sct_register<T> cntr{“cntr”};
explicit A(const sc_module_name& name) : sc_module(name) {
targ.clk_nrst(clk, nrst);
cntr.clk_nrst(clk, nrst);
SC_METHOD(checkProc); sensitive << targ << cntr;
}
void checkProc() {
cntr.reset();
// Register accumulates received data up to N
if (cntr.read() > N) {
cntr.write(0);
} else
if (targ.get(data)) {
cntr.write(cntr.read()+data);
}
}Read register in thread process should be done carefully. If register value is checked to generate an output or change a state, it could lead to incorrect behavior in TLM, if there is no other activation source for the process.
sct_register<T> cntr{“cntr”};
sct_initiator<T> init{"init"};
explicit A(const sc_module_name& name) : sc_module(name) {
cntr.clk_nrst(clk, nrst);
init.clk_nrst(clk, nrst);
SCT_THREAD(cntrProc); sensitive << cntr << init;
async_reset_signal_is(nrst, 0);
// cntr is assigned in some method process
}
// Probably incorrect version
void cntrProc() {
init.reset_put();
wait();
while (true) {
if (cntr.read() > 10) { // In TLM mode no process activation
init.b_put(cntr.read()); // until cntr value changed
}
wait();
}
}
// Correct version
void cntrProc() {
init.reset_put();
T lastCntr = 0;
wait();
while (true) {
// Request sent only when cntr value changed
if (cntr.read() > 10 && cntr.read() != lastCntr) {
lastCntr = cntr.read();
init.b_put(cntr.read());
}
wait();
}
}The same problem is actual for sct_signal.
sct_clock<> is implementation of clock source (generator) like sc_clock with enable/disable control.
/// Enable clock activity, clock is enabled after construction
void enable();
/// Disable clock activity, can be called at elaboration phase to disable
/// clock at simulation phase start
void disable();
/// Register clock gate signals/ports to control clock activity.
/// If any of the signals/ports is high, then clock is enabled
void register_cg_enable(sc_signal_inout_if<bool>& enable);
/// Get clock period
const sc_time& period() const;Clock gate cell sct_clock_gate_cell and clock signal sct_clk_signal should be used together to connect clock input to gated clock source. sct_clk_signal is special signal without DC delay in written value becomes readable.

The code example illustrates using sct_clock_gate_cell and sct_clk_signal.
SC_MODULE(A) {
sc_in_clk SC_NAMED(clk);
sc_in<bool> SC_NAMED(nrst);
sc_in<bool> SC_NAMED(clk_enbl);
sct_clk_signal SC_NAMED(clk_out);
sct::sct_clk_gate_cell SC_NAMED(clk_gate);
sc_in<bool> SC_NAMED(clk_in);
explicit A(const sc_module_name& name) : sc_module(name) {
clk_gate.clk_in(clk); // Clock input
clk_gate.enable(clk_enbl); // Gate clock input
clk_gate.clk_out(clk_out); // Gated clock output
clk_in(clk_out);
SCT_THREAD(thrdProc, clk_in, nrst); // Use clock input bound to gated clock
async_reset_signal_is(nrst1, 0);
}};Clock gate cells can be sequentially connected to each other, gated clock output of one cell bound to clock input of anther cell.
In TLM mode is all thread processes are created with SC_THREAD/SCT_THREAD macros, clock source(s) can be disabled. Disabling sct_clock allows to speed simulation:
sct_clock<> clk{"clk", 1, SC_NS};
explicit A(const sc_module_name& name) : sc_module(name) {
if (SCT_CMN_TLM_MODE) {
clk.disable();
}
}In thread process reset logic initializes registers, local variables and output signals. This logic should be placed in reset section (code scope before first wait()).
sct_out<T> o{"o"};
sct_signal<T> s{"s"};
void thrdProc() {
// Reset section
int a = 0; // Local variable
s = 0; // Register
o = 0; // Output
wait();
while (true) {
...
wait();
}
}In method process initialization logic initializes local variables and output signals. This logic is normally be placed in the beginning of the process.
sct_out<T> o{"o"};
void methdProc() {
// Initialization section
int a = 0; // Local variable
o = 0; // Output
...
a = i + 1;
if (s) o = a;
}Initialization logic in method process could be merged with its behavior logic based on inputs and registers. Such code style can have better simulation performance.
sct_in<T> i{"i"};
sct_out<T> o{"o"};
void methdProc() {
int a = i+1; // Local variable
o = a ? s : 0; // Output
...
}The communication channels also need to be reset with specified reset(), reset_get() and reset_put() methods. In thread process every channel used in this process should be initialized in the reset section.
sct_initiator<T> init{"init"};
sct_target<T> targ{"targ"};
sct_fifo<T, 2> fifo{"fifo"};
void thrdProc() {
init.reset();
targ.reset();
fifo.reset_put(); // If FIFO used for put
fifo.reset_get(); // If FIFO used for get
fifo.reset(); // If FIFO used for get and put both
wait();
while (true) {
...
wait();
}
}In method process every channel used in this process is initialized in the beginning of the process or assigned at all execution path in the process code. Having no explicit reset for registers, signals, output ports and synchronizers can improve simulation performance.
sct_initiator<T> init{"init"};
sct_target<T> targ{"targ"};
sct_register<T> reg1{"reg1"};
sct_register<T> reg2{"reg2"};
void methProc() {
init.reset();
reg1.reset();
T val = targ.get(); // targ is accessed at all path, no reset required
if (val > 0) {
reg1 = val; // reg1 accessed at some paths only, reset required
init.put(val); // init accessed at some paths only, reset required
}
reg2 = val + 1; // reg2 is accessed at all path, no reset required
}Reset signal can be asserted/de-asserted in TB and DUT processes as well. To have the same simulation time in RTL and TLM modes it needs to follow the rules given in this section.
If reset control thread is in TB, it could control reset based on time period and be non-sensitive to any channels. In this case such a thread should be SC_CTHREAD in RTL mode and SC_THREAD in TLM mode. To avoid extra activation in TLM mode, this thread should wait for a specified time instead of clock events.
SC_MODULE(A) {
SC_CTOR(A) {
// Thread not sensitive to anything
#ifdef SCT_TLM_MODE
SC_THREAD(resetProc);
#else
SC_CTHREAD(resetProc, clk_in.pos());
#endif
}
#define rstWait(N) if (SCT_CMN_TLM_MODE) wait(N, SC_NS); else wait(N);
void resetProc() {
nrst = 0;
rstWait(3);
cout << sc_time_stamp() << " " << sc_delta_count() << " de-assert reset\n";
nrst = 1;
rstWait(5);
...
}
};If reset control thread is sensitive to any channels, it should be SCT_THREAD and have dont_initialize() in RTL mode. Such a thread can also be a normal test thread which provides stimulus and checks results:
SC_MODULE(A) {
SC_CTOR(A) {
// Thread sensitive to SS channels
SCT_THREAD(resetProc, clk);
#ifndef SCT_TLM_MODE
dont_initialize();
#endif
sensitive << s;
}
sct_signal<unsigned> s{"s"};
void resetProc() {
nrst = 0;
while (s.read() < 3) {s = s.read()+1; wait();}
cout << sc_time_stamp() << " " << sc_delta_count() << " de-assert reset\n";
nrst = 1;
}Clock edge and reset level normally are the same for the design. To update them for whole design SCT_CMN_TRAITS should be defined:
#define SCT_CMN_TRAITS SCT_NEGEDGE_POSRESET // Set negative edge and positive reset levelTo specify clock edge and reset level for individual library modules, template parameters should be used, for example:
sct_target<T, SCT_NEGEDGE_POSRESET> run{"run"};
sct_initiator<T, SCT_POSEDGE_NEGRESET> resp{"resp"};Array of SingleSource channels can be implemented with sc_vector. First parameter of sc_vector is name, second parameter is number of elements (should be a compile time constant). To provide additional parameters to single source channels, it needs to use lambda function as third parameter of sc_vector.
static const unsigned N = 16;
using T = sc_uint<16>;
sc_vector<sct_target<T>> targ{"targ", N}; // Two parameters
sc_vector<sct_initiator<T>> init{"init", N, // Three parameters
[](const char* name, size_t i) { // Lambda function
return sc_new<sct_initiator<T>>(name, 1); // Initiator with sync register
}}; Target and Initiator can be instantiated in top module to be connected to the correspondent modules in testbench. Such top module is synthesizable with input/output ports for the Target/Initiator instances.
Top module can contain Target which is not always ready and has no synchronous register. Top module can contain initiator which has no synchronous register. Top module cannot contain MultiTarget or MultiInitiator. Vector (sc_vector) of Target/Initiator in top module is supported.

To connect testbench Target/Initiator to the correspondent top module Initiator/Target normal bind function is used always except multi-language simulation. For multi-language simulation if DUT is in SystemVerilog and testbench is in SystemC language, the simulation tool generates a special SystemC wrapper for DUT top module. To connect this wrapper to SystemC testbench SCT_BIND_CHANNEL macro should be used. SCT_BIND_CHANNEL macro cannot be applied to Target/Initiator with record type.
// Include DUT module generated wrapper or SystemC header
#ifdef RTL_SIM
#include "DUT.h" // Multi-language simulation, include generated wrapper
#else
#include "MyDut.h" // SystemC simulation and synthesis, include designed header
#endif
template<class T>
class MyModule : public sc_module {
DUT dut{"dut"};
sct_target<T> targ{"targ"};
SC_CTOR(MyModule) {
// Bind targ to init in dut module
#ifdef RTL_SIM
SCT_BIND_CHANNEL(dut, init, targ); // Multi-language simulation
#else
targ.bind(dut.init); // SystemC simulation and synthesis
#endif
}
}Array of Targets/Initiators supported in any module including top module. Instead of C++ array sc_vector should be used (C++ array is not supported).
To bind the Targets/Initiators SCT_BIND_CHANNEL macro with 4 parameters is provided.
// Include DUT module generated wrapper or SystemC header
#ifdef RTL_SIM
#include "DUT.h" // Multi-language simulation, include generated wrapper
#else
#include "MyDut.h" // SystemC simulation and synthesis, include designed header
#endif
template<class T, unsigned N>
class MyModule : public sc_module {
DUT dut{"dut"};
sc_vector<sct_target<T>> targ{"targ", N};
SC_CTOR(MyModule) {
// Bind all elements of targ to elements of init in dut module
#ifdef RTL_SIM
SCT_BIND_CHANNEL(dut, init, targ, N); // Multi-language simulation
#else
for (unsigned i = 0; i != N; ++i)
targ[i].bind(dut.init[i]); // SystemC simulation and synthesis
#endif
}
}Target and Initiator can be connected through module hierarchy from child module up to parent module. To do that sc_port of Initiator/Target in parent module should be used.
Ports (sc_port) of Target/Initiator contain pointer to them. To bind Initiator to Target through ports it needs to use get_instance() method which provides Target/Initiator from its port (see example below).

template<class T>
struct Child : public sc_module {
sct_target<T> run{"run"};
sct_initiator<T> resp{"resp"};
};
template<class T>
struct Parent: public sc_module {
// Target/initiator ports
sc_port<sct_target<T>> run;
sc_port<sct_initiator<T>> resp;
Child<T> child{"child"};
explicit Parent(const sc_module_name& name) : sc_module(name) {
// Bind ports to child module target/initiator
run(child.run);
resp(child.resp);
}
};
struct Top: public sc_module {
Parent<T> parent{"parent"};
explicit Parent(const sc_module_name& name) : sc_module(name) {
// Bind child target/initiator to eahc other through parent ports
parent.run->bind(parent.resp->get_instance()); // get_instance() provides Initiator from its sc_port
}
};Process which calls Target/Initiator functions should be in the module where Target/Initiator declared. If a process calls Target/Initiator through its port (sc_port<sct_target>/sc_port<sct_initiator>) the process module and target initiator module should be synthesized in the same parent module.
Conventional cycle accurate design modules can be mixed with single source modules without limitations. sct_clock should be used instead of normal sc_clock.
Cycle accurate threads created with SC_CTHREAD macro are activated by clock event. Such processes can use SingleSource channels to communicate to each other and SingleSource threads created with SCT_THREAD macro. Cycle accurate processes should be sensitive to all the SingleSource channels used inside.
template<class T>
class MyModule : sc_module {
sct_target<T> in{"in"};
sct_signal<T> s{"s"};
sct_fifo<T, 2> fifo{"fifo"};
MyModule(const sc_module_name& name) : sc_module(name) {
SC_CTHREAD(threadProc, clk);
sensitive << in << fifo.PUT << s; // sensitivity to all used channels
async_reset_signal_is(nrst, 0);
}
void threadProc() {
in.reset_get();
fifo.reset_put();
wait();
while (true) {
if (in.request()) {
fifo.put(s.read());
in.get();
}
wait();
}
}
}Instead of SC_CTHREAD special SCT_CTHREAD can be used. SCT_CTHREAD supports clock edge with third parameter:
SCT_CTHREAD(proc, clk, clk_edge); // clk_edge could be 0 -- negedge, 1 -- posedge, 2 -- both edges
SCT_CTHREAD(proc, clk); // clk_edge is SCT_CMN_TRAITS::CLOCK
Record is supported as data type in all SingleSource channels. The record should comply SystemC requirements for records used in signal/port: the record should have default constructor w/o parameters, operator==(), operator<<(std::ostream) and sc_trace() implemented.
struct Rec_t {
bool enable;
sc_uint<16> addr;
// Default constructor
Rec_t() : enable(false), addr(0) {}
// Another constructor, optional
Rec_t(bool enable_, sc_uint<16> addr_) : enable(enable_), addr(addr_) {}
bool operator == (const Rec_t& other) const {
return (enable == other.enable && addr == other.addr && indx == other.indx);
}
};
namespace std {
inline ::std::ostream& operator << (::std::ostream& os, const Rec_t& r) {
os << r.enable << r.addr << r.indx; return os;}
}
namespace sc_core {
void sc_trace(sc_trace_file* , const Rec_t& , const std::string&) {}
}
...
sct_target<Rec_t> run{"run"};
void methProc() {
run.reset_get();
if (run.request()) {
Rec_t data = run.get(); // Get record fields from target
}
}See more examples at https://github.com/intel-innersource/frameworks.design.systemc.sct-common/tree/singlsrc_timed/test/records/
Development and most of the debug is intended to be done in RTL mode. After RTL mode tests passed, TLM mode could be used for faster simulation. If TLM mode behavior differs from RTL mode, it could be debugged with C++ debugger or in commercial simulators.
To debug TLM mode in the simulation tool, EXTR_SIM_DEBUG option should be defined for syscan tool:
syscan ... -cflags "-DEXTR_SIM_DEBUG" ... EXTR_SIM_DEBUG option enables debug signals in sct_prim_fifo which implements target, initiator and FIFO in TLM mode. There are core_req, core_ready and core_data signals, similar to signals between target/initiator.
sct_prim_register which implements register in TLM mode has curr_val which is value to be read. sct_prim_synchronizer which implements synchronizer in TLM mode has curr_val and next_val. curr_val is value to be read, next_val is just written value.
TLM mode simulation in the simulation tool can be used as normal SystemC simulation.