|
|
3 Processes
As mentioned in the introduction SystemC support 3 types of processes, SC_METHOD, SC_THREAD and SC_CTHREAD. The last one, SC_CTHREAD will not be discussed in this tutorial as its usage is limited.
The difference between the processes is simple an straightforward:
An SC_METHOD process is executed whenever an event occurs on its sensitivity list. The process then run to completion before returning control back to the SystemC scheduler (process is suspended until the next event). You can consider an SC_METHOD to be similar to a VHDL process with a static sensitivity list, and similarly you can not have a wait() statement inside a SC_METHOD.
An SC_THREAD process is a process that is only called once by the SystemC scheduler. The process is suspended when it hits a wait() statement, when this happens the control is relinquished back to the SystemC scheduler. The process continues on a new event which can either be specified in the wait() statement (dynamic sensitivity), on the sensitivity list (static sensitivity) or both! This can not be done in VHDL, you either use the static sensitivity list as specified in the process() argument or you use the wait() statement but you can't have both.
Both process types are created during the linking stage, or in simulation terms during elaboration. Although not discussed in this tutorial you can also create processes dynamically during simulation.
SC_METHOD simulates faster than SC_THREAD.
3.1 Example FF using SC_METHOD and SC_THREAD
The code fragment below shows the same implementation of our dff Flip-Flop using an SC_METHOD and a SC_THREAD process.
|
// SystemC FlipFlop using SC_METHOD
|
// SystemC FlipFlop using SC_THREAD |
As with the previous example, you specify SC_THREAD in the constructor SC_CTOR(dff). Because the SystemC scheduler will call process p1 only once we need an infinite loop (unless of course you want to run it only once). In the above example I have use while(true) but you can also use other C/C++ constructs like for(;;).
The position of the wait() statement, that is, before or after the dout assignment, requires some further explanation. In contrast to a VHDL simulator the SystemC scheduler will call each process during the start of the simulation (at time 0), this is done independent of any events! If you don't want this to happen then you can use the SystemC dont_initialize() call as shown below:
SC_CTOR(dff) {
SC_THREAD(p1); // call p1 only once
dont_initialize(); // don't call p1 at time 0
sensitive << clk.pos();
}
It is important to understand dont_initialize() and the position of wait(). Consider the following 2 FF simulation models, both behave like a FF except when simulation starts.
void p1() {
while(true) { // Infinite loop
wait(); // Note Position!
dout.write(din.read());
}
}

Result of using wait() before dout.write()
void p1() {
while(true) { // Infinite loop
dout.write(din.read());
wait(); // Note Position!
}
}

Result of using wait() after dout.write
This result is perhaps not what you expects, dout gets the din value even before the first rising clock edge. These kinds of issues can affect verification so be aware of them.
3.2 Sensitivity List
We already have used one form of the sensitivity list in the dff examples. The sensitivity-list list all signals, ports and events that the process should trigger on. There are several coding styles that you can use for the sensitivity list:
sensitive << clk; // Trigger on both rising and falling edge of clk, i.e. clk'event
sensitive_pos << clk; // rising_edge(clk), Note: this form is deprecated in 2.2 but still supported
sensitive << clk.pos(); // rising_edge(clk)
sensitive_neg << clk; // falling_edge(clk), Note : this form is deprecated in 2.2 but still supported
sensitive << clk.neg(); // falling_edge(clk)
sensitive(clk); // Not recommended since you can only pass on 1 argument
sensitive(clk,reset); // Illegal, result in a compile error
sensitive << clk << reset; // Correct
3.2.1 Coding Style
Process attributes like sensitive and dont_initialize only affect the preceding process macro (SC_THREAD/SC_METHOD). It is therefore advisable to indent these attributes to highlight to which process they belong too, for example:
SC_CTOR(dff) {
SC_THREAD(p1);
dont_initialize();
sensitive << clk.pos();
SC_THREAD(p2);
SC_METHOD(p3);
sensitive << clk.pos() << reset;
}
3.3 Process Synchronization and Deltas
Like in VHDL, SystemC processes can be synchronised using signals. A SystemC signal, sc_signal is a templated class (discussed later) which requires the type to be passed on between the angles brackets. SystemC signals follow the same evaluation/update sequence as in VHDL, that is the signal value is updated minimally 1 delta cycle later.
To observe the effects of delta delay, consider the 2 Flip-Flop shift register as shown in the circuit below. Looking at the VHDL example first, the second FF's clock input is connected to the clk input port via a combinatorial clock assignment, this is a common VHDL coding error!

The SystemC and VHDL code for the circuit is shown below:
|
#include <systemc.h> |
library ieee; use ieee.std_logic_1164.all ; entity delta is port (clk : in std_logic; din : in std_logic; dout : out std_logic); end delta; architecture rtl of delta is signal q_s : std_logic:='0'; signal clk_s : std_logic; begin process (clk) begin if rising_edge(clk) then q_s <= din; end if ; end process ; clk_s <= clk; -- delta delay! process (clk_s) begin if rising_edge(clk_s) then dout <= q_s; end if ; end process ; end rtl; |
Looking at the VHDL code first you would expect the output dout to behave like a 2 Flip-Flop shift register, however as shown in the Modelsim waveform below the dout output is updated at the same time as q_s behaving as if the second Flip-Flop is just a wire.

Modelsim 6.3a Simulation Results
To explain the simulation result we look at the SystemC example. Assume we have a rising edge on clk, this will trigger both process p1 and p3. During the first evaluation phase of the SystemC kernel the new values for q_s and clk_s are being scheduled. During the following update phase q_s and clk_s are updated. The SystemC kernel will continue to go through the evaluation/update cycle until there are no more runable processes. In our case clk_s has been updated which will trigger another evaluation/update cycle. Process p2 is now executed and during this evaluation/update cycle dout get the value of q_s, however, this value has already been updated during the first evaluation/update loop! After this there are no more runable processes and the simulation time advances. The result of this is that q_s and dout_s are updated during the same timestep and the second FF behaves like a wire.

SystemC Simulation Results
The source files for this test can be found in the delta_ff_example subdirectory.
Apart from using signals to synchronise processes, SystemC also has some additional types called events. Events will not be discussed in this tutorial but are easily picked up from other sources on the web.
3.4 Process Variables
If you want to use the equivalent of VHDL process variables in your code than you simply declared the types in your SC_MODULE without sc_signal. The example below shows a simple Toggle Flip-Flop in SystemC and VHDL. One flip-flop uses a variable and one a signal, the SystemC "!" operator is equivalent to a VHDL NOT operator.
|
#include <systemc.h> SC_MODULE(tff) { sc_in<bool > clk; sc_out<bool > dout1; sc_out<bool > dout2; sc_signal<bool> sig1; bool var2; // equivalent to a VHDL var void p1() { // using signals sig1.write(!sig1.read()); dout1.write(sig1.read()); } void p2() { // using variables var2=!var2; dout2.write(var2); } SC_CTOR(tff) { sig1.write(true); // init var2=false; // init SC_METHOD(p1); sensitive << clk.pos(); dont_initialize(); SC_METHOD(p2); sensitive << clk.pos(); dont_initialize(); } }; |
library ieee; use ieee.std_logic_1164.all; entity tff is port (clk : in std_logic; dout1 : out boolean; dout2 : out boolean); end tff; architecture rtl of tff is signal sig1 : boolean:=TRUE; begin process (clk) begin if rising_edge(clk) then sig1 <= NOT sig1; dout1 <= sig1; end if ; end process ; process (clk) variable var2:boolean:=FALSE; begin if rising_edge(clk) then var2 := NOT var2; dout2 <= var2; end if ; end process; end rtl; |

Observe that dout1 and dout2 have the same phase (delta cycles). Also note the init values for var2=false and sig1=true, in SystemC you initialise variables/signals during the construction phase (when SC_CTOR is called). As a VHDL programmer you might have thought of using something like this:
sc_signal<bool > q_s=false; ** Error **
bool var=true; ** Error **
This will generate a compile error as it is invalid C++. As mentioned before SC_MODULE is a user defined C++ type (a class) and therefore you have to use the constructor to assign initial values.
tff DUT("tff"); // Instantiate Device Under Test, call default constructor, assign initial values
3.5 SC_HAS_PROCESS
As discussed in the previous chapter SC_MODULE, SC_CTOR etc are all macros and not SystemC keywords. They can be used to hide some verbose looking C++ syntax and to provide some info to the SystemC kernel. There are however occasions were you have to use the C++ syntax instead of these macros. One of those occasions is when you want to pass on some argument to the constructor. In this case you can't use the SC_CTOR macro. If this happens and you are using processes in your module then you need to use the SC_HAS_PROCESS macro to inform the SystemC kernel that you are using processes.
For example, suppose you want to create a counter module that start counting at different values when instantiated (using generics in VHDL). The code fragment below shows such a module.
#ifndef COUNT_H
#define COUNT_H
#include <systemc.h>
SC_MODULE(count) {
sc_in<bool > clk;
sc_out<int > dout;
int cnt;
void p1() {
dout.write(cnt++); // simple counter
}
count (sc_module_name _n, int _cnt) : sc_module(_n), cnt(_cnt) {
SC_METHOD(p1);
sensitive << clk.pos();
dont_initialize();
}
count (sc_module_name _n) : sc_module(_n) { // same as SC_CTOR(count){ !
cnt=8;// init during default construction
SC_METHOD(p1);
sensitive << clk.pos();
dont_initialize();
}
SC_HAS_PROCESS(count); // Required if processes are used but SC_CTOR is not called
};
#endif
This module count can be instantiated in another module as follows:
count DUT1("dut1"); // Using default constructor, start counting at 8
count DUT2("dut2", 32); // Instantiate Device Under Test, start counting at 32
The Macro SC_CTOR(count) is equivalent to count (sc_module_name _n) : sc_module(_n). The constructor name (count) must be the same name as used in the SC_MODULE argument. In VHDL when you instantiate a component you must specify the instance name followed by the component name. This is the same in SystemC, the component name is count and the instance name is DUT1 and DUT2. In SystemC you pass on an additional string name (sc_module_name) which is normally the same as the instance name (dut1 and dut2 in the above example).
In the above example we are not using the SC_CTOR macro but we are using a process so we need to add the SC_HAS_PROCESS(module_name).