The ForSyDe-SystemC Tutorial for the Synchronous MoC
This tutorial is a walk-through for modeling systems in the Synchronous (SY) Model of Computation (MoC) using the ForSyDe-SystemC library. It assumes that the user has already installed the SystemC and the ForSyDe-SystemC libraries and knows how to build and run a model on her computer.
In ForSyDe-SystemC, a system is modeled as a hierarchical process network where processes communicate only via signals, making it similar to a data-flow style of modeling. We will introduce different elements of the SY MoC in ForSyDe-SystemC with examples.
In order to avoid confusion, note that although ForSyDe-SystemC uses the SystemC kernel to simulate the models, the modeling style is different from SystemC. Many elements of the SystemC language are not present1, and the ones which are used may appear in a different terminological context.
The running example for this tutorial is a simple multiply-accumulator system with two inputs and a single output. The main input is a ramp signal and the other input fixes the multiplication coefficient.
The Synchronous (SY) MoC
Before proceeding with implementing the first system using the SY MoC of ForSyDe-SystemC, a few basic concepts need to be clarified:
- the SY MoC describes no quantitative notion of time thus it assumes that neither communication nor computation takes time.
- two events may be either synchronous (they happen at the same moment) or one unambiguously precedes the other.
- as other synchronous programming languages do (Lustre, Esterel, Argos, etc), ForSyDe describes events as ‘‘present’’ or ‘‘absent’’. A key property is that the order of these events is absolute and unambiguous.
- two signals can be considered synchronous if all events in one signal are synchronous with the events from the other signal and vice-versa.
- the SY MoC is widely used for modelling control systems or digital systems, where the design ignores (abstracts) timing details.
A good example to understand the SY MoC is the design style of sequential digital circuits in HDLs. The designer assumes of a master ‘‘clock’’ event which triggers the registers. By doing so, it enables the internal processes which are assumed to execute in ‘‘delta delay’’ (zero delay), thus abstracting the notion of physical time.
Signals are the only means with which the processes can communicate and synchronize together. Each signal must be bound to an input and an output port of a ForSyDe process. We will see how to do this later in this tutorial. Signals are typed. In the SY MoC, signals are defined using the class
ForSyDe::SY::signal) and the token carried by a signal can be either absent, denoting an absent event or a value of the signal type. There is a helper (template) class ´abst_ext
defines a signal called
my_sig which carries tokens of type
abst_ext class has a set of accompanying methods and functions which can be used to check for and set presence of values.
Processes are the basic computational elements in ForSyDe-SystemC. In the general case, a process receives its inputs via input signals, performs some calculations on them, and communicates the result as a single output. This single output may be an ‘‘n’‘-tuple which can be later unzipped using another process. Hence, this does not affect the generality of the case. Each process belongs to a single MoC which governs how the data is communicated to, and out of a process. A process in a process network is either a ‘‘leaf process’’, which is a leaf of the process network hierarchy, or a ‘‘composite process’’, which is the result of interconnection of other basic and composite processes. In the SY MoC, leaf processes can be classified either as ‘‘combinational’’ if they do not include any internal state, or ‘‘sequential’’ if they do.
Leaf processes are created using process constructors. Process constructors are templates provided by the library that are parameterized in order to create a process. The parameters to a process constructor can be initial values (e.g., initial states) or functions. From the C++ point of view, creating a leaf process out of a process constructor is equivalent to instantiating a C++ class and passing the required parameters to its constructor. Definition of functions used to pass to each process constructor usually happen in a separate header file for each process.
Consider the case of a simple process representing a multiplier which multiplies its two integer inputs, and has an integer output that represents the result. There is no internal state involved in this example which makes it a combinational leaf process. Looking at the ForSyDe-SystemC library documentation, we can find that the process constructor for a two input combinational process is
ForSyDe::SY::scomb2. It requires a function to be passed to it, which applied in each evaluation cycle to the inputs in order to produce the outputs. The function is defined in
The pragma statements enclose the core functionality of the function and can be used by external analysis/synthesis tools which later on operate on the intermediate representation generated by ForSyDe-SystemC models. They can be safely ignored for simulation-only purposes.
Now, in order to create the multiplier process (probably as a part of a composite process) we can have:
The types of inputs and the output are specified as template parameters of the class. The name of the process (SystemC module) together with the multiplication function are passed as parameters of the class constructor in the order defined in the library API.
Processes are connected to signals using their ports. As for basic process constructors, port names are automatically chosen inside the library according to the following scheme. Output ports of the basic process constructors are called
oport2, and so on. Similarly, input ports are also named as
More complex processes, can be constructed by composing leaf or other composite processes. A composite process is simply the result of instantiation of other processes and wiring them together using signals. In order to have a valid ForSyDe model, a set of rules should be respected. Remember that if we ignore some of these rules, we may still be able to simulate our model using the SystemC kernel. But, the result will not benefit from the ForSyDe benefits such as formal analysis, composability, etc.
- A composite process is in fact a SystemC module derived from the
- A composite process is the result of instantiation and interconnection of other valid ForSyDe processes, no ad-hoc SystemC processes or modules are allowed.
- Ports of all child processes in a composite process are connected together using signals of SystemC channel type
- A composite process in the includes zero or more inputs and output ports of types
- If an input port of a composite process should be connected to several child processes, an additional fanout process (i.e.,
ForSyDe::SY::fanout) is needed in between.
Let us put all these information into action and build a composite multiply-accumulator (MAC) process out of other basic processes.
Creating the adder and the delay processes is similar to creating the multiplier process shown above, but based on the
comb4 process constructors. The SystemC-provided macros
SC_CTOR are used to create a SystemC module that plays the role of a ForSyDe composite process. Ports of appropriate types are specified at the beginning followed by declaration of interconnection signals. A signal of type
ForSyDe::SY::signals is needed for each point-to-point connection with according type. In the constructor of the composite process (
SC_CTOR), the child processes are created, followed by their interconnection using the intermediate signals.
Reducing the Code Verbosity
Looking at the definition of the
mulacc composite process presented above, we can identify a couple of points where we are repeating ourself which makes the code verbose. Among them:
- Class template type parameters for
delay-based processes are repeated twice. In fact these types can be inferred by the type of signals connected to their inputs and outputs.
- During process instantiation in each case, we bind all the input ports and the output port of the processes to at least one signal.
By exploiting the type-inference mechanism in the C++(11) language, ForSyDe-SystemC library provides helper functions to construct leaf processes and bind a channel to their inputs and outputs. The names of these helper functions are in the form
xyz is the name of the original process constructor. In addition to the parameters taken by the process constructor, these helper functions take one signal for each of the process inputs/outputs as arguments and binds it to the respective port. Additional signal binding can still be done using the port names. The
mulacc composite process can be rewritten as:
The auto keyword is a newly-added feature of the latest C++11 standard which in this case automatically infers the type of the
add1 process (pointer).
Test-benches and Simulation
In order to test a process, one can simply look at the top level process as a design under test, provide inputs according to the semantics of the MoC that it belongs to and observe the outputs. There exist processes in each MoC that only produce/consume values and can be used for this purpose.
Consider the case of our MulAcc example. A very simple testbench can be constructed by providing a constant integer value 3 to the first input and a ramp signal starting from 1 to the second input. Below you can see how we can implement this simple testbench for our MulAcc example.
constant process constructor has only one output. It takes an initial value and the number of ticks to run (and no functions) as the parameter and produces a process which outputs this value in each cycle. The
source process constructor is more powerful and also takes a function (shown below) additionally. It repeatedly applies the function to the initial value to generate the next output.
sink process constructor has only one input. It takes a function as its parameter and passes the received input to this function.
To run the simulation, we need to instantiate the top level composite process and run the SystemC simulation kernel.
Compilation of the project is just like any other C++ application. Depending on the way you have built your SystemC library (statically or dynamically), you compile the code and link against SystemC to yield a single executable file. By running the resulting executable file, we are in fact simulating our system. For example, in a Unix-based shell we could have:
user@host:~/code/mulacc$ ./run SystemC 2.3.0-ASI --- Oct 31 2012 09:07:54 Copyright (c) 1996-2012 by all Contributors, ALL RIGHTS RESERVED output value: 3 output value: 9 output value: 18 output value: 30 output value: 45 output value: 63 output value: 84 output value: 108 output value: 135 output value: 165 user@host:~/code/mulacc$
A distinguishing feature of the !ForsyDe-SystemC library is the ability of the constructed models to exports their own internal structure in a machine-readable intermediate representation. This enables the designers to easily pass the validated (by simulation) models to external analysis and synthesis tools, without the need for building a C++ front-end. By adding a SystemC hook to the start of the simulation phase of SystemC for the top model of the design, the executable models dump their internal process network structure in an XML format.
The top-level model of the mulacc design is modified below to export the model structure.
Note that the library (and the above model) are enabled for introspection only when the
FORSYDE_INTROSPECTION macro is defined, either in the source code or as a compiler switch (
-DFORSYDE_INTROSPECTION). Assuming the existence of the
gen/ sub-folder, the executable model writes to XML files named
mulacc.xml to this folder, each representing one level of hierarchy.
As a simple usage, for visualization purpose, we have converted these XML files into DOT format using the tool f2dot. The results are displayed below. These graphs are comparable withe the figure presented in the beginning of the tutorial.
or in better words, are not allowed to be used in ForSyDe-SystemC ↩