Simple adder ipblock
From Gezel2
Here is an example how you can develop your own ipblock. An ipblock in GEZEL is a mechanism to generate new simulation primitives (in C++) that look like black boxes in GEZEL. We develop a small adder, and show an example of the GEZEL code, the C++ code, and the compilation commands.
GEZEL code
Below is the GEZEL code. It includes an ipblock definition (ipblock ablock ..) which selects an ipblock of type 'myblock', and which passes a parameter 'thisparam=thatval'. The ipblock is included in a small testbench that feeds an incrementing sequence of numbers on the 'a' input and a constant value 2 on the 'b' input.
ipblock ablock(in a, b : ns(8); out c : ns(8)) {
iptype "myblock";
ipparm "thisparam=thatval";
}
dp top {
sig a, b, c : ns(8);
reg a1 : ns(8);
use ablock(a, b, c);
always {
a = a1;
b = 2;
$display("a ", a, " b ", b, " c ",c);
a1 = a1 + 1;
}
}
system S {
top;
}
C++ code
We need to design a C++ class to describe the implementation of this ipblock. The following file shows this C++ class. This class MUST be called myblock. This is how the simulator figures out the correspondence between the 'myblock' mentioned in the GEZEL file, and the C++ implementation to be used for 'myblock'. Moreover, the class MUST be compiled into a shared library called 'libmyblock.so' (more on that below).
class myblock : public aipblock {
public:
myblock(char *name);
void setparm(char *);
void run();
void out_run();
bool checkterminal(int n, char *tname, aipblock::iodir d);
bool cannotSleepTest();
bool needsWakeupTest();
};
We will discuss each of the methods below.
- The constructor initializes the class. All ipblock inherit from a baseclass aipblock. The default initialization for aipblock is to pass it the instance name (which will be 'ablock' when parsing the GEZEL file above).
myblock::myblock(char *name) : aipblock(name) {}
- The setparm method is called for each 'ipparm' that is mentioned in the GEZEL file. The kind and number of ipparm is user-defined, and depends on the application. In this example, we simply print the parameter and return.
void myblock::setparm(char *_name) {
cerr << "set parm: " << _name << "\n";
}
- The run method is called once per cycle. This method must read the inputs and produce corresponding outputs. There is a predefined array 'ioval[]' (actually, a vector<gval *>) for each input/output on an ipblock. The matching between inputs/outputs in GEZEL and C++ is positional: the first input or output is connected to ioval[0], the second to ioval[1], and so forth. Since this is a simple adder block, the run method will simply add two inputs and return the result. Note that gval is GEZELs arbitrary-length internal data type. Consult the source code for the operations available on gval (listed in gval.h).
void myblock::run() {
ioval[2]->assignulong(ioval[0]->toulong() + ioval[1]->toulong());
}
- The out_run method is used when an ipblock uses registered outputs. It is empty in this case since we are developing a combinatorial block.
void myblock::out_run() {
}
- The checkterminal method is called by the parser for each input and output of the ipblock. The calling order follows the position of inputs and outputs. This function performs a simple check if the user has included the inputs and outputs with the correct direction and port name.
bool myblock::checkterminal(int n, char *tname, aipblock::iodir d) {
switch(n) {
case 0 :
return (isinput(d) && isname(tname, "a"));
break;
case 1 :
return (isinput(d) && isname(tname, "b"));
break;
case 2 :
return (isoutput(d) && isname(tname, "c"));
break;
}
return false;
}
- The cannotSleepTest and needsWakeupTest methods control the simulator scheduler. For typical ipblock (like the current one), you make them return default values: false for cannotSleepTest and true for needsWakeupTest. With more advanced ipblock such as cosimulation interfaces, these methods enable control of the GEZEL scheduler from within a foreign simulator. Refer to the GEZEL User Manual or study the gplatform source code for more information.
bool myblock::cannotSleepTest(){
return false;
}
bool myblock::needsWakeupTest(){
return true;
}
- Finally, the create_myblock C function (which MUST be called create_myblock, ie. the suffix must match the class name) instantiates a myblock class and returns a pointer to it. It is part of the dynamic-class-loading mechanism.
extern "C" aipblock *create_myblock(char *instname) {
return new myblock(instname);
}
Simulation
Before simulation can start, the C++ code must be compiled into a shared library. This is done with the following commands. Note the -fPIC flag during compilation and the -shared flag during linking. The exact paths to use will depend on the installation directories used for GEZEL.
g++ -fPIC -O3 -I/opt/gezel-2.0//include/gezel -c myblock.cxx
g++ -shared -O3 -Wl,--rpath -Wl,/home/schaum/gezel/devel/build/lib \
myblock.o /opt/gezel-2.0/lib/libipconfig.so \
/opt/gezel-2.0//lib/libfdl.so -lgmp -ldl -o libmyblock.so
Once you have the libmyblock.so, you can simply start the simulator (fdlsim). When an ipblock is encountered which is not available within the simulator, the dynamic-link library path will be searched for a library of a matching name. If an ipblock of type 'myblock' is needed, a search is done for a shared library 'libmyblock.so'. When the library is found, it is linked-in to the simulation and the create_myblock function is called. All of this is transparent for the user. Here is the simulation output.
/opt/gezel-2.0//bin/fdlsim ipb.fdl 5 set parm: thisparam=thatval a 0 b 2 c 2 a 1 b 2 c 3 a 2 b 2 c 4 a 3 b 2 c 5 a 4 b 2 c 6
Ipblock give considerable flexibility in design and simulation development. They allow GEZEL to be integrated into a bigger simulation setup without having to open up the simulator internals. Very often, when you find that GEZEL cannot describe a particular feature or situation, an ipblock may be the solution.
