-
Notifications
You must be signed in to change notification settings - Fork 40
Single Source library
Single Source library consists of communication channels with functional interfaces.
Target and Initiator in pair connect two processes in different modules. FIFO connects two processes in the same module as well as serves as a storage for one process. Buffer is a fast version of FIFO to be used in sequential process(es) only. Pipe is used to pipeline computation for multiple cycles. Register adds state for combinational process.
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 provides fast simulation, intended for functional and performance simulation. Approximate time simulation is data-driven, there are no clock events.
- 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_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_buffer.h -- buffer channel, which is a fast FIFO to use in clocked process(es) only
- sct_pipe.h -- pipeline register
- sct_register.h -- register to store METHOD state
- sct_clock.h -- clock with enable/disable
- sct_sel_type.h -- integer types
sct_int
andsct_uint
- sct_static_log.h -- static logarithm implementation
- sct_utils.h -- utility functions
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
By default, 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
#endif
If 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 the channel is ready to put request |
void reset_put() |
Reset this channel | |
void clear_put() |
Clear (remove) request put in this cycle | |
bool put(const T& data) |
Non-blocking put request into the channel if it is ready, return ready to request | |
bool put(const T& data, sc_uint<N> mask) |
Non-blocking put request into the channel 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 the channel has request to get |
void reset_get() |
Reset this channel | |
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 the channel, return current request data, if no request last data returned | |
bool get(T& data, bool enable) |
Non-blocking get request and remove it from the channel 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 LENGTH | |
unsigned elem_num() |
Number of elements in the channel, value updated last clock edge for METHOD, last DC for THREAD | |
bool almost_full(unsigned N) |
Return true if the channel has (LENGTH-N) elements or more, value updated last clock edge for METHOD, last DC for THREAD | |
bool almost_empty(unsigned N) |
Return true if the channel has N elements or less, 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 the channel | |
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 | |
void addToGet(sc_sensitive& s) |
Add get related signals to process sensitivity | |
sct_in_if | const T& read() |
Read from the signal/register |
void addTo(sc_sensitive* s, sc_process_handle* p) |
Add signals to process sensitivity | |
sct_inout_if | const T& read() |
Read from the signal/register |
void write(const T& val) |
Write to the 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.
Instead of addTo
- operator <<
can be used. Instead of addToPut
, addToGet
and addPeekTo
- operator <<
with 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 of them:
-
SCT_METHOD(proc)
-- combinational method process, same asSC_METHOD(proc)
, -
SCT_METHOD(proc, clk)
-- sequential method process with synchronous reset, -
SCT_METHOD(proc, clk, rst)
-- sequential method process with asynchronous reset, -
SCT_THREAD(proc)
-- sequential thread process, same asSC_THREAD(proc)
, -
SCT_THREAD(proc, clk)
-- sequential thread process with explicit clock, -
SCT_THREAD(proc, clk, rst)
-- sequential thread process with explicit clock/reset.
SCT_METHOD(proc)
creates a combinational process operates with single source channels, signals/ports.
SCT_METHOD(proc, clk)
and SCT_METHOD(proc, clk, rst)
creates a sequential method process.
SCT_THREAD(proc)
creates a sequential process operates with single source channels only.
SCT_THREAD(proc, clk)
creates a process operates with single source channels and reads single source signals (sct_signal
) / ports (sct_in
/sct_out
).
SCT_THREAD(proc, clk, rst)
creates a universal sequential process which can access single source channels and/or reads single source signals (sct_signal
) / ports (sct_in
/sct_out
).
SC_CTHREAD
macro is normally not used.
Difference between sequential method and thread processes is in simulation speed, method process is faster. Thread process allows to use wait()
to introduce multiple states, which can simplify the process function code. Sequential method process function should have reset section and sequential logic section, and cannot have wait()
calls.
void combMethod() {
// combinational logic ...
}
void seqMethod() {
if (rst) {
// reset logic ...
} else {
// sequential logic ...
}
}
void seqThread() {
// reset logic
wait();
while (true)
// sequential logic ...
wait();
// optional sequential logic ...
// multiple wait() calls allowed
}
}
Clock and reset levels for a process are specified by SCT_CMN_TRAITS
if clock/reset are explicitly provided. Otherwise clock and reset levels are taken from channels in the sensitivity list. Channels obtains clock and reset levels from SCT_CMN_TRAITS
by default, that can be changed for individual channels.
All the channels used in a sequential process should use the same clock and edge as the process sensitive. A channel could have different reset or reset level than the process.
If a thread process has reset signal(s), it should have the reset specification with async_reset_signal_is
or/and sync_reset_signal_is
.
Any process should be sensitive to all single source channels accessed and to all single source signals (sct_signal
) / ports (sct_in
/sct_out
) read in its function code. Combinational method should be also sensitive to all SystemC signals (sc_signal
) and ports (sc_in
/sc_out
) read in its function code. A process is never sensitive to reset.
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) {
SCT_METHOD(combMethod); // Combinational method, same as SC_METHOD
sensitive << init; // No reset in sensitivity
SCT_METHOD(seqMethod, clk); // Sequential method with synchronous reset
sensitive << init;
SCT_METHOD(seqMethod, clk, nrst); // Sequential method with asynchronous reset
sensitive << init;
SCT_THREAD(seqThread); // Sequential thread, sensitive to @sct_target only
sensitive << targ; // Clock and reset taken from @sct_target
async_reset_signal_is(nrst, 0); // Reset specification required
SCT_THREAD(seqThread, clk); // Sequential thread, sensitive to @sct_target and @sct_signal
sensitive << targ << s; // Clock is explicitly provided for @sct_signal
async_reset_signal_is(nrst, 0); // Reset specification required
SCT_THREAD(seqThread, clk, nrst); // Most universal sequential thread, sensitive to @sct_signal here
sensitive << s; // Clock and reset are explicitly provided
async_reset_signal_is(nrst, 0); // Reset specification still required
}
};
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 channels
In 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
.
Level enable signal is often used by one process to make another process does some actions. The signal is usually boolean type. It can have a specific level (usually high) for one or several cycle that means some action to be done. Such behavior work well in cycle accurate mode, but for fast simulation mode requires event notification at every cycle. To support this event notification sct_signal
and sct_in
/sct_out
have second template parameter ENABLE_EVENT
. Also special level-enable signal and ports of boolean type are introduced: sct_enable_signal
, sct_enable_in
, and sct_enable_out
.
Level-enable singal in fast simulation mode notifies event to wake a sensitive thread process every clock cycle which is taken from the process. Combinational method process cannot be sensitive for such a signal or port.
In the following example producer process send multiple sequenced event.
SC_MODULE(MyModule) {
sct_target<unsigned> SC_NAMED(targ);
SC_CTOR(MyModule) {
SCT_THREAD(producer, clk, nrst);
sensitive << enable << targ;
async_reset_signal_is(nrst, 0);
SCT_THREAD(consumer, clk, nrst);
sensitive << enable;
async_reset_signal_is(nrst, 0);
}
sct_enable_signal SC_NAMED(enable);
void producer() {
targ.reset_get(); enable = 0;
wait();
while (true) {
unsigned N = targ.b_get();
enable = 1;
while (N--) wait();
enable = 0;
wait();
}
}
void consumer() {
wait();
while (true) {
if (enable) {...}; // Do something
wait();
}}}
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
Using synchronous register in request path (sync_valid
) not allowed if put process is sequential thread or method. Using synchronous register in response path (sync_ready
) not allowed if get process is sequential thread or method. That is required to have equivalent behavior in the generated SV code.
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.
Using FIFO in method process(es) with both sync_valid
and sync_ready
set to 0
is prohibited as that results in combinational loop.
Using FIFO in one method process is allowed with sync_valid
and sync_ready
both set to 1
only. If sync_valid
or sync_ready
set to 0
, such FIFO can be used in two different method processes.
Initiator process | Target process | sync_valid | sync_ready | Minimal FIFO size |
---|---|---|---|---|
method | method | 0 | 0 | prohibited |
method | method | 0 | 1 | 1, two processes |
method | method | 1 | 0 | 1, two processes |
method | method | 1 | 1 | 2 |
method | thread | 0 | 0 | 1 |
method | thread | 1 | 0 | 2 |
method | thread | 0 | 1 | not supported |
method | thread | 1 | 1 | not supported |
thread | method | 0 | 0 | 1 |
thread | method | 0 | 1 | 2 |
thread | method | 1 | 0 | not supported |
thread | method | 1 | 1 | not supported |
thread | thread | 0 | 0 | 2 |
thread | thread | 0 | 1 | not supported |
thread | thread | 1 | 0 | not supported |
thread | thread | 1 | 1 | not supported |
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", 0, 1}; // Pipelining register for response
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());
}
}
}
Buffer is a channel kind of FIFO to be used in single or two sequential processes. Buffer implements sct_fifo_if
interface, the same as FIFO.
Buffer differs from FIFO in higher simulation speed which is achieved by implementation it as primitive channel (inheritor of sc_prim_channel
).
Buffer has one common implementation for cycle accurate and approximate time modes.
Buffer size can be 2 elements or more.
template<
class T,
unsigned LENGTH, // Size (maximal number of elements)
class TRAITS = SCT_CMN_TRAITS // Clock edge and reset level traits
>
class sct_buffer {};
Buffer constructor has the same parameters as FIFO has. Parameters sync_valid
and sync_ready
should be 0 (false)
.
sct_buffer(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
Buffer can be used by one process to store data and for inter-process communication between two processes. peek()
function of Buffer can be called from any process including combinational method process.
Pipeline register (sct_pipe
) is intended to pipeline combinational logic and enable re-timing feature of a logic synthesis tool. In generated SystemVerilog code it is replaced with a component from a logic synthesis tool and an external library.
Pipeline register can be used in one method or thread process as well as put in one process and get in other process. Pipeline register is normally added to sensitivity list of process where put or get done.
Pipeline register supports put bubbles and get backpressure. If there is no get, but some empty registers, they are shifted to provide next request put.
The pipeline register implements sct_fifo_if
. It has size template parameter which is a positive number.
template<
class T,
unsigned N, // Number of pipeline registers, one or more
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_pipe {};
The pipeline register can have input or output registers, which are not used for re-timing.
sct_pipe(const sc_module_name& name,
bool addInReg, // Add input register not moved by re-timing
bool addOutReg, // Add output register not moved by re-timing
const std::string& rtlName) // Pipeline register instantiated component name
Typical use case for pipeline register is combinational logic re-timing in method process.
void methProc() {
run.reset_get();
resp.reset_put();
pipe.reset();
if (pipe.ready() && run.request()) {
T data = compute(run.get()); // Heavy computation to be pipelined
pipe.put(data);
}
if (pipe.request() && resp.ready()) {
resp.put(pipe.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);
}
}
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 SingleSource 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 level
To 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.
For synchronous register in a top module Target/Initiator externally connected to a testbench, ICSC reports the corresponding error.
To connect a 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.
For synchronous register in a testbench Target/Initiator connected to the DUT SCT_BIND_CHANNEL
macro reports the corresponding error at elaboration phase of simulation.
// 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. That is possible explicitly or with sc_port
of Initiator/Target.
There is an example of explicit binding Target to Initiator through module hierarchy:
template<class T>
struct Child : public sc_module {
sct_target<T> run{"run"};
};
template<class T>
struct Parent: public sc_module {
Child<T> child{"child"};
SC_CTOR(Parent) {}
};
SC_MODULE(Top) {
sct_initiator<T> resp{"resp"};
Parent<T> parent{"parent"};
SC_CTOR(Top) {
parent.child.run.bind(resp);
}};
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"};
};
template<class T>
struct Parent: public sc_module {
sc_port<sct_target<T>> run;
Child<T> child{"child"};
SC_CTOR(Parent) {
run(child.run); // Bind port to child module initiator
}};
SC_MODULE(Top) {
sct_initiator<T> resp{"resp"};
Parent<T> parent{"parent"};
SC_CTOR(Top) {
resp.bind(parent.run->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.
Connection between modular interfaces inside of common parent module can be done with FIFO. One modular interface should have a FIFO instance and other modular interface should have a FIFO port (sc_port< sct_fifo<> >
).
The same can be done if the FIFO is instantiated in the parent module.
template<class T, unsigned N>
struct A : public sc_module, sc_interface {
sct_fifo<T, N> SC_NAMED(fifo);
...
};
template<class T, unsigned N>
struct B : public sc_module, sc_interface {
sc_port<sct_fifo<T, N>> SC_NAMED(fifo_port);
...
};
struct Parent : public sc_module{
A<int, 3> SC_NAMED(a);
B<int, 3> SC_NAMED(b);
SC_CTOR(Parent) {
b.fifo_port(a.fifo); // Bind FIFO port to FIFO instance
}
};
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/
RTL simulation provides precise result and is equivalent to simulation of the generated SV. TLM simulation is approximate time, i.e. channels in TLM mode have implementation optimized for speed.
TLM FIFO is equivalent to RTL FIFO, so there is no difference. TLM Target/Initiator pair is equivalent to RTL only for default parameters (no always ready, no sync registers, no FIFO added). If always ready used, sync registers, or FIFO added, TLM Target/Initiator pair differs from RTL one.
TLM Pipe is implemented as FIFO (sct_prim_fifo
) that significantly differs from RTL implementation.
TLM mode can be used whenever exact time of simulation events is not important. TLM mode can be used for functional modelling such as hardware or software debugging. For performance modelling TLM mode can be used if 100% precise result is not required, otherwise RTL mode should be used.
If a design is used in TLM mode, it needs to ensure process sensitivity lists follow special rules. In TLM mode any process is activated by event notification from SingleSource channels and SystemC signals/ports. To catch an event notification, the correspondent process should be waiting for this event. That requires both of the following:
- All the events are added into process sensitivity list;
- Activated process goes to a next state where an event notification happens in some future.
To satisfy first condition is it enough to add all channels accessed in the process into process sensitivity list. The second condition is more tricky and requires analysis of all inter-process communications. Lets discuss the second condition in more details.
Process activation events can be notified by the process itself and by other processes (for sequential process only). Other process notifies the events independently or in response to our process actions. The means any process activation should notify enough events (no event could be enough) to get notification of its activation events back.
Initiator, Target and FIFO notifies the sequential or combinational process which performs put or get one more time. In the next example threadProc
executes infinitely by self-notification. That allows the process puts to non-full FIFO multiple times w/o get from this FIFO.
Sequential process example.
// sct_single_fifo/method_test3.h
void threadProc() {
fifo.reset_put();
wait();
while (true) {
fifo.b_put(val); // Activate this process again and again
wait();
}
}
Combinational process example.
// sct_single_fifo/method_test3.h
void methPutProc() {
mfifo.reset_put();
if (mfifo.ready()) mfifo.put(val);
}
void methGetProc() {
mfifo.reset_get();
// Do nothing to let @methPutProc fill the FIFO
}
In the following example there are two processes which activates one to each other through the FIFO. In producerProc
the FIFO is accessed to notify the event for consumerProc
in state 0 and 1. consumerProc
is activated by the FIFO event and access (get) the FIFO which notifies producerProc
back. In state 2 where no event is notified, so consumerProc
is not activated and producerProc
is not activated more -- simulation hangs up.
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(); // STATE 0
while (true) {
if (fifo.ready()) {
fifo.put(getSomeVal());
}
wait(); // STATE 1
// Do nothing, no event notified this cycle
wait(); // STATE 2
}
}
void consumerProc() {
fifo.reset_get();
if (fifo.request()) {
doSomething(fifo.get());
}
}
The next example demonstrates sequential process which activates itself with FIFO channel. Any put and get to/from the FIFO notifies the event to activate the process. In this process any value except 42
is put to the FIFO. If value is 42
, nothing is put and no event is notified, so the process is not activated and simulation hangs up.
struct Top : public sc_module {
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) {
T val = getSomeValue();
if (fifo.ready() && val != 42) {
fifo.put();
} else {
// Do nothing if @val is 42, no event notified this cycle
}
wait();
if (fifo.request()) {
doSomething(fifo.get());
}
}
}
The next example shows how to create a counter process with signal channel only. Using sct_signal
instead of sc_signal
is required to avoid process is sensitive to it in RTL mode.
struct Top : public sc_module {
sct_signal<T> cnt{"cnt"};
explicit Top(const sc_module_name& name) : sc_module(name) {
SCT_THREAD(cntProc, clk.pos()); // Second parameter required for RTL mode to know clock edge
sensitive << cnt; // Process read and write signal
async_reset_signal_is(nrst, 0);
}
}
void cntProc() {
cnt = 0;
wait();
while (true) {
// Updating @cnt notifies the process itself
cnt = (cnt.read() != 255) ? cnt.read() + 1 : 0;
wait();
}
}
A typical mistake in TLM mode is waiting for negotiation of a channels ready or request. In the example below, if the FIFO is ready no event is notified. As soon as FIFO could be ready more than one cycle, that could leads to simulation hangs up.
void storeProc() {
fifo.reset();
wait();
while (true) {
...
if (!fifo.ready()) {
// Do something that activates other processes
} else {
// Do nothing, no event notified this cycle
}
wait();
}
}
Joining multiple request and ready channel status in one condition normally is correct.
...
if (fifo.ready() && init.request()) {
// Do something that activates other processes
} else {
// Do nothing, no event notified this cycle
}
...
As soon as timing diagram in TLM mode can differ from RTL mode, it is prohibited to process logic assumes the specific number of cycles. Instead of that, process logic and process interconnect should based on operations with channels. The following example has a magic wait which can lead to different result in RTL and TLM modes.
...
if (fifo.ready()) {
wait(3); // Can lead to incorrect results
fifo.get();
}
...
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.