GEZEL Library Blocks

From Gezel2

Jump to: navigation, search

GEZEL supports predesigned library blocks. At the outside, these look like datapath modules, and they can be used in the same way. However, their inside is not written in GEZEL code. Instead, the behavior of library blocks is written in C++ and compiled directly into the GEZEL kernel. This enables blocks that run much faster than cycle-true models in GEZEL, for example by raising the simulation abstraction level. It also allows to introduce features in a GEZEL simulation that are not supported in GEZEL code, such as special types of IO or host system function calls. This chapter presents the use of GEZEL library blocks. This includes the general modeling and usage properties of library blocks, as well as a catalog of available library blocks. And finally, you can also introduce your own library blocks into the GEZEL kernel.

Contents

Library Blocks Definition

GEZEL library blocks are created with the keyword ipblock. They define three elements. First, they define their IO interface, just as a datapath module does. Second, they define their type. And third, they define an optional number of parameters. Consider the RAM library block as an example. This block is part of the standard configuration GEZEL kernel, and is used to simulate a RAM memory. It has an outline that correponds to RAM cell; it define an address bus, a data input and — output bus, and read/write control lines. The RAM library block is parametrizable in size and wordlengh, as well. The following GEZEL definition creates a RAM block of 32 positions, of 8-bit words.

 ipblock M(in address : ns(5);
           in wr,rd   : ns(1);  
           in idata   : ns(8);
           out odata  : ns(8)) {
   iptype "ram";
   ipparm "wl=8";  
   ipparm "size=32"; 
 }

A library block with name M is created. It defines five ports. including the address bus (address), write and read control strobes (wr, rd), an input data bus (idata) and an output data bus (odata). Next is an indication of the library block type, in the iptype statement. Then, a number of library block parameters can be given. These are dependent on the type of the library block. For a RAM, the worldlength of the databus (wl) and the number of memory locations (size) can be specified. GEZEL will issue a warning when a parameter field is found that is not supported by the library block. The names as well as the order of the ports of a library block are determined by the type. For a particular type, there is only one ordered set of names, with each port having a predefined direction. For a library block of type ram for example, the first port must be called address and it must be an input. GEZEL will issue a warning when there is a mismatch detected. Once a library block is instantiated, it can be used like any other datapath module. Library blocks can be included within other datapaths using the use statement (See GEZEL Datapath Design). The next program fills up the RAM module defined above, and next reads it out again.

A RAM library block testbench

 ipblock M(in address : ns(5);
           in wr,rd   : ns(1);  
           in idata   : ns(8);
           out odata  : ns(8)) {
   iptype "ram";
   ipparm "wl=8";  
   ipparm "size=32";  
 }
 
 dp tmac(out address : ns(5);
         out wr, rd   : ns(1);
         out idata    : ns(8); 
         in  odata    : ns(8)) {
    reg ar       : ns(5);
    reg idr, odr : ns(8);
 
    sfg write  { wr = 1; rd = 0; idata = idr; odr = odata; address = ar;  
      $display($cycle, ":ar ", ar, " idata ", idata);    
    }
    sfg read   { wr = 0; rd = 1; address = ar; odr = odata; idata = idr;  
      $display($cycle, ":ar ", ar, " odata ", odata);
    }
    sfg incadr  { ar = ar + 1; idr = idr + 1;}
    sfg clraddr { ar = 0; }
 }
 
 fsm ftmac(tmac) {
   state   s1;
   initial s0;
   @s0 if (ar == 4) then (write, clraddr ) -> s1;
       else   (write, incadr)  -> s0;
   @s1 if (ar == 4) then (read,  clraddr)  -> s0;
       else   (read,  incadr)   -> s1; 
 }
 
 dp sysRAM {
   sig adr  : ns(5);
   sig w, r : ns(1);
   sig i, o : ns(8);
   use M   (adr, w, r, i, o);
   use tmac(adr, w, r, i, o);
 }
 
 system S {
   sysRAM;
 }

The testbench that drives the RAM module is included in lines 10—34. The first five locations of the RAM are first written with an increasing number sequence, and next these five locations are read out again.

Catalog of Library Blocks: GEZEL Kernel Blocks

The following table enumerates the different library blocks. Some library blocks, such as cosimulation interfaces, are part of a particular simulator configuration.

Library Blocks in the GEZEL Kernel (available for all programs)
ram Function The RAM block implements a RAM cell with separate read and write control strobes, and a separate data input and data output. One read and one write access is possible per clock cycle.
IO address input, ns(log2(size)), holding the address for the RAM.
wr input, ns(1). write asserted high.
rd input, ns(1), read asserted high.
idata input, ns(wl), input data bus.
odata output, ns(wl), output data bus.
Parameters wl wordlength of data bus (wl>0)
size number of RAM locations.
tracer Function The tracer block implements equivalent functionality as the $trace directive, and records the values of a signal into a file at each clock cycle.
IO data input, ns(userdefined), data input
Parameters file quoted string with filename
wl wordlength of numbers in file
rom Function Contributed by A.V.Lorentzen and J.Steensgaard-Madsen, DTU (Denmark's Technical University). Originating from an implementation of Andrew Tanenbaum's Mic-1 that conforms to Ray Ontko's Java simulator for it.

The ipblock may represent an initialised Mic-1 microcontrol store. The contents may come from a file generated by the microprogram assembler provided by Ray Ontko. Beware of possible endianness problems if you want to generate the contents differently.

IO address input, ns(log2(size)) holding an address of the least number of 8-bit bytes capable of holding one word of wl-bits (left justified)
rd input, ns(1), read asserted high
odata output, ns(wl), output data
Parameters size number of locations
wl wordlength
file name of file with initial contents
startbyte (default 4), number of bytes to skip in file
ijvm Function Contributed by A.V.Lorentzen and J.Steensgaard-Madsen, DTU (Denmark's Technical University). Originating from an implementation of Andrew Tanenbaum's Mic-1 that conforms to Ray Ontko's Java simulator for it.

The ipblock may represent a Mic-1 store with an ijvm-program preloaded. The file from which the preloaded program is read may be generated by the ijvm-assembler provided by Ray Ontko. The current version conforms completely to Ray's programs, including the restriction to just two code-sections. Parameters sp, lv, and cpp must be bound to the initial values of the Mic-1 registers with these names. They do not need to be set to the values chosen in Ray Ontko's Java simulator.

IO address input, ns(log2(size)), holding the address of a 32-bit data word
wr input, ns(1), write asserted high
rd input, ns(1), read asserted high
idata input, tc(32), input data bus
odata output, tc(32), output data bus for values
bytes input, ns(log2(size)) holding the address of a 4-bytes code sequence
fetch input, ns(1), read asserted high
byteval output, ns(32), output data bus for code
Parameters size number of locations
file name of file with initial contents
sp initial value of register stack pointer register
lv initial value of local variables register
cpp initial value of constant pool pointer register
filesource Function The filesource block allows to fetch stimuli from an external file. Wordlength and representation base are parametrizable. The block has a variable number of data ouputs. When the user wires this block up with for example two outputs, then two values will be read from a file for each clock cycle of simulation.
IO d1 first output
d2 optional second output
.. up to 10 optional outputs are supported
Parameter file string, name of the file to read.
wl integer, wordlength
base interger, base in which values in the file are expressed. Symbols of the set [0-9a-z] are used to represent values in non-decimal bases.
rand16 Function A 16-bit random number generator.
IO o output data

Catalog of Library Blocks: gplatform Blocks

armsystem Function ARM Core + program memory. The ISS is SimIt-ARM.
Parameters exec Name of the statically linked ELF binary to be executed on the ARM verbose. When set to 1, this ARM will execute in verbose (debug) mode, visualizing all system calls as they proceed.
period Relative clock period, default 1. When set to e.g. 2, the ARM will run at half speed relative to the system (gezel) clock.
armsystemsource Function Memory-mapped cosimulation interface for an ARM core intercepting memory writes on this core.
IO data data output, ns(32)
Parameters core Name of the armsystem block this cosimulation interface is connected to.
address Address decoded by this cosimulation interface.
armsystemsink Function Memory-mapped cosimulation interface for an ARM core intercepting memory reads from this core.
IO data data input, ns(32)
Parameters core Name of the armsystem block this cosimulation interface is connected to.
address Address decoded by this cosimulation interface.
armsystemprobe Function This block allows the application software running on the ARM ISS to send messages directly to another ipblock (through the probe function as discussed in Section 8.4 on page 75).
IO t
Parameters probe Address of the ARM address space that points to the command string for the probe.
block Target library block that this probe must send messages to.
armbuffer Function Dual-port Shared memory for ARM-GEZEL communications
IO idata input, ns(32), data channel from GEZEL to RAM
odata output, ns(32), data channel from RAM to GEZEL
address input, ns(32), RAM address
wr input, ns(1), write strobe
Parameters core string, indicates the ARM that the RAM is controlled by
address base address in the ARM memory space for the RAM
range number of locations in the RAM
armfslmaster Function Fast-Simplex-Link interface for GEZEL-to-ARM communication. A FSL interface is often used in the context of a Microblaze processor. Due to the unavailability of a Microblaze ISS, we have emulated the FSL interface on top of a StrongARM memory-mapped interface. This allows one to perform functional verification of GEZEL coprocessors before attaching them to a MicroBlaze.
IO data input, ns(32), data channel from GEZEL to ARM
full output, ns(1), indicates if ARM can accept data
write input, ns(1), write strobe to transfer data from GEZEL
Parameters core string, name of the ARM that the interface is connected to
read address (hex), memory-mapped location where the data value can be read in the ARM memory space. For each memory read from this address, a handshake sequence on the full/write pins will be automatically completed. Note that the armfslslave does not implement an actual FSL interface, but rather emulates one with memory-mapped reads.
armfslslave Function Fast-Simplex-Link interface for ARM-to-GEZEL communication. A FSL interface is often used in the context of a Microblaze processor. Due to the unavailability of a Microblaze ISS, we have emulated the FSL interface on top of a StrongARM memory-mapped interface. This allows one to perform functional verification of GEZEL coprocessors before attaching them to a MicroBlaze.
IO data output, ns(32). Data channel from ARM to GEZEL
exists output, ns(1). Flag that indicates availability of data.
read input, ns(1). Read strobe from GEZEL
Parameters core string. Indicates the name of the armsystem that this block connects to
write address (hex), indicates where to write data. For each memory write, the handshake sequence on exists/read will be automatically completed.
armsfu2x2 Function Special-function-unit interface with 2 input, 2 output. The use of this block requires the use of Simit-ARM-2.1-sfu as simulator. The blocks are triggered by means of special instructions. See 'special function units' under cosimulation.
IO d1 out, ns(32), data port 1 with op2x2 operand
d2 out, ns(32), data port 2 with op2x2 operand
q1 in, ns(32), data port 1 with op2x2 result
q2 in, ns(32), data port 2 with op2x2 result
Parameters core string, name of core this interface is attached to.
device integer (0 or 1) - there are two possible SFU of each type
armsfu2x1 Function Special-function-unit interface with 2 input, 1 output. The use of this block requires the use of Simit-ARM-2.1-sfu as simulator. The blocks are triggered by means of special instructions. See 'special function units' under cosimulation.
IO d1 out, ns(32), data port 1 with op2x1 operand
d2 out, ns(32), data port 2 with op2x1 operand
q1 in, ns(32), data port 1 with op2x1 result
Parameters core string, name of core this interface is attached to.
device integer (0 or 1) - there are two possible SFU of each type
armsfu3x1 Function Special-function-unit interface with 3 input, 1 output. The use of this block requires the use of Simit-ARM-2.1-sfu as simulator. The blocks are triggered by means of special instructions. See 'special function units' under cosimulation.
IO d1 out, ns(32), data port 1 with op3x1 operand
d2 out, ns(32), data port 2 with op3x1 operand
d3 out, ns(32), data port 3 with op3x1 operand
q1 in, ns(32), data port 1 with op3x1 result
Parameters core string, name of core this interface is attached to.
device integer (0 or 1) - there are two possible SFU of each type
picoblaze Function Picoblaze core + program memory. This is a fully functional picoblaze core, based on kpicosim, the PicoBlaze ISS by Mark Six. The port definition follows the outline of an actual PicoBlaze for easy integration of VHDL code.
IO port_id output, ns(8), port address
i8051system Function i8051 core + program memory
Parameters exec Name of the intel-hex formatted i8051 binary to execute
verbose When set to 1, run the ISS in verbose (debug) mode. period Relative clock period, default 1. When set to e.g. 2, the 8051 will run at half speed relative to the system (gezel) clock.
i8051systemsource Function Port-mapped cosimulation interface to transport data from 8051 to GEZEL.
IO data output, ns(32), data output.
Parameters core name of the i8051system core this port-mapped interface belongs to.
port quoted string, one of P0, P1, P2, P3.
i8051systemsink Function Port-mapped cosimulation interface to transport data from GEZEL to 8051 to GEZEL.
IO data input, ns(32), data input.
Parameters core name of the i8051system core this port-mapped interface belongs to.
port quoted string, one of P0, P1, P2, P3.
i8051buffer Function Dual-port shared memory for 8051, mapped onto the X-bus
IO idata input, ns(8), data from GEZEL into RAM
odata ouput, ns(8), data from RAM to GEZEL
address in, ns(x), RAM address
wr in, ns(1), RAM write strobe
Parameters core string, name of 8051 dp that this shared ram is connected to
xbus integer, 8051 extended address serving as RAM base address
xrange integer, number of locations in this shared resource

Catalog of Library Blocks: SystemC Blocks

systemcsource Function Cosimulation interface to transport data from SystemC to GEZEL.
IO data output, ns(32), data channel from SystemC to GEZEL
Parameters var string, indicates the symbolic name of the corresponding SystemC channel.
systemcsink Function Cosimulation interface to transport data from GEZEL to SystemC.
IO data input, ns(32), data channel from GEZEL to SystemC
Parameters var string, indicates the symbolic name of the corresponding SystemC channel.

Custom Library Blocks

Finally, you can add custom library blocks to the GEZEL kernel. Adding custom library blocks allows you to cope with a variety of design problems. Some examples are as follows.

  • Including legacy C code (jpeg code, crypto libraries) in a GEZEL simulation.
  • Adding new cosimulation interfaces, for example including socket or IPC communication primitives to enable network-based cosimulation.
  • Adding advanced I/O capabilities, for example formatting blocks that create graphical output either directly on the screen or else into a file.
  • Adding advanced runtime analysis capabilities, such as a block that records the histogram of values on a bus.

There are three steps to take in order to create a new custom library block. First, you must decide how the outline of the block looks like, as well the parameter set you will support with it. Next, you have to develop the behavior of the block in C++. And finally, you have to compile the block as a shared library so that it can be linked in by your application.

Step 1 - Design the outline and functionality

The first step is to decide on the outline of the block. Indeed, before starting to write C++ code, it is useful to write out in GEZEL code how the block will look like and think about the desired behavior.

As an example, we will develop a runlength encoding block. A runlength encoder creates a compact, tuple-based representation of a sequence of numbers. For example, if a runlength encoder reads the number string

  1, 1, 1, 3, 4, 4, 6, 6, 6, 6

Then it would produce a tuple sequence with (value, count) tuples as

  (1, 3), (3, 1), (4, 2), (6, 4)

We will use an outline that looks as follows.

 ipblock my_rle(in data : ns(8); 
                out tupdat : ns(8);
                out tupnum : ns(8)) {
   iptype “rle";
   ipparm “maxlen=32";
 }

The block reads 8-bit data input values and performs runlength encoding on them. The block has two outputs that will provide runlength-encoded data. The type of the block is rle (runlength encoder), and it supports one parameter call maxlen. This number holds the maximum runlength that we’ll allow before a codeword is forced. In the example we allow a maximum runlength of 32. This means that, if the input data would consist of 34 consecutive zeroes, then we expect the output to consist of two runlength tuples, one (0,32) and the next (0,2).

There is still one issue to address. Library blocks are cycle-true functions. This means we need to develop the function in such a way that it can read input and produce output each clock cycle. For a runlength encoder, an output will not be available each clock cycle however. We will deal with this situation in our runlength encoder by producing dummy output tuples for which tupnum equals to zero. We thus can express the behavior of the runlength encoder in pseudocode as follows. intialize:

   previous_input_data = not_a_number;
   runlength = 0;
 execute:
   read input data;
   (tuplenum, tupledata) = (0,0);
   if (input_data == previous_input_data) {
      runlength = runlength + 1;
      if (runlength == maxlen) {
        (tuplenum, tupledata) = (runlength, input_data);
        runlength = 0;
      }
   } else {
     if (runlength != 0) {
      (tuplenum, tupledata) = (runlength, previous_data);
     }
     runlength = 1;
   }
   previous_input_data = input_data;
   write (tuplenum, tupledata);

Step 2 - Design the C++ implementation

We are now ready to design the block into a GEZEL library block. Library blocks are derived from a baseclass aipblock. This block has a number of virtual functions that can be user-defined in derived classes.

 class aipblock {
  protected:
   enum iodir {input, output};
  public:
   vector<gval *> ioval;
   aipblock(char *_name);
   virtual ~aipblock();
   virtual void run();
   virtual void setparm(char *_name);
   virtual bool checkterminal(int n, char *tname, iodir dir);
   virtual bool needsWakeupTest();
   virtual bool cannotSleepTest();
   virtual void touch();
 };
  • The vector ioval contains the values appearing on the actual ports. The first element of this vector corresponds to the first port, the second element to the second port, and so on.
  • The function run is called each clock cycle to execute the block.
  • The function setparm is called when the GEZEL parser finds a field ipparm. The argument of this function contains the quoted string that is found in the GEZEL code. For example, when the GEZEL code contains ipparm “maxlen=32��? then the argument of setparm will be “maxlen=32��?.
  • The function checkterminal is called by GEZEL for each port. It allows to verify that the user of the GEZEL block has used the correct names and directions of the ports of this block. The function returns a boolean, which must return true of no problem is found. The arguments of the function correspond to the data found in the GEZEL program. n holds the port index, with the first port having index 0. tname holds the name the user of the block has used for the port. dir indicates if it is an input or an output.
  • The functions needsWakeupTest() and cannotSleepTest() are used to support the sleep mode of the GEZEL simulator (See Section 4.1 on page 31). When the simulator is running, each clock cycle the function cannotSleepTest() is called. This function needs to return true if sleep mode cannot be started. Once the simulator is in sleep mode, the function needsWakeupTest() is called every skipped clock cycle. The function returns true when the GEZEL simulation needs to wake up again. The function touch is used in the context of cosimulation interfaces, to force the next call to needsWakeupTest to return true. To get insight into these different functions, it is best to study one of the cosimulation interfaces of the GEZEL tools. For example, file arm_itf.cxx in armcosim .

The next listing shows how to program the runlength encoder as a derived class from the base class aipblock.

A runlength encoder library block for GEZEL

 #include "ipblock.h"
 
 class rle : public aipblock {
   int previous_data_value;
   int runlength;
   int maxlen;
 public:
   rle(char *name) : aipblock(name) {
     previous_data_value = -1;
     runlength = 0;
     maxlen = 256;
   }
   void run() {
     ioval[1]->assignulong(0);
     ioval[2]->assignulong(0);	
     if (ioval[0]->toulong() == (unsigned) previous_data_value) {
       runlength = runlength + 1;
       if (runlength == maxlen) {
         ioval[1]->assignulong(runlength);
         ioval[2]->assignulong(ioval[0]->toulong());
         runlength = 0;
       }
     } else {
       if (runlength != 0) {
         ioval[1]->assignulong(runlength);
         ioval[2]->assignulong(previous_data_value);	
       }
       runlength = 1;
     }
     previous_data_value = ioval[0]->toulong();
   }
   bool checkterminal(int n, char *tname, aipblock::iodir dir) {
     switch (n) {
     case 0:
       return (isinput(dir) && isname(tname, "data"));
       break;
     case 1:
       return (isoutput(dir) && isname(tname, "tuplenum"));
       break;
     case 2:
       return (isoutput(dir) && isname(tname, "tupledata"));
       break;
     }
     return false;
   }
   void setparm(char *_name) {
     gval *v = make_gval(32,0);
     if (matchparm(_name, "maxlen", *v)) 
       maxlen = v->toulong();
     else
       printf("Error: rke does not recognize parameter %s\n",_name);
   }
   bool cannotSleepTest() {
     return false;
   }
 };
  
 extern "C" aipblock *create_rle(char *instname) {
   return new rle(instname);
 }         
  

The constructor (lines 8—12) and the runtime function (lines 13—31) correspond to the initialization part and the execution part of the pseudocode shown earlier. The data type of the ioval array is gval (defined in gval.h of the GEZEL release). The functions assignulong and toulong provide conversions from and to C data types. Of course, these conversions can loose precision if the GEZEL wordlength exceeds that of the wordlength of a C long.

The port verification method checkterminal in lines 32—45 checks if the port names chosen by the GEZEL user correspond to the ones we have chosen for the runlength encoder outline, as shown earlier. The setparm method in lines 46—52 accepts parameters, if any. The function call matchparm is a member of aipblock and helps in parsing the parameters. Finally, the function cannotSleepTest illustrates the minimal implementation for a block that does not affect the sleep-mode mechanism of the GEZEL simulator.

Finally, the create_rle function tells how to instantiate an RLE class. This function will be called after the dynamic library that contains the RLE class is loaded into memory. By default, this function should only instantiate the class and return a pointer to it.

Step 3 - Integrate the block and test it

The final step is to integrate the C++ code of the library block into GEZEL. GEZEL supports dynamically linked library blocks. You can compile the C++ code into a shared library. At runtime, the GEZEL kernel will link in these library blocks at the moment you need them. Compilation into a shared library block proceeds as follows. In these commands, BUILD must be substituted with the path the the GEZEL installation.

 g++ -fPIC -O3 -Wall -c -IBUILD/include/gezel iprle.cc
 g++ -shared -O3 -Wl,--rpath -Wl,BUILD/lib iprle.o BUILD/lib/libipconfig.so \
             BUILD/lib/libfdl.so -lgmp -ldl -o librle.so

This will create a shared library librle.so. When you run the GEZEL simulation (fdlsim or gplatform), this library must be available in the directory where the simulation is run. It will be automatically read in at the moment the rle ipblock is used by your simulation.

An example GEZEL program that uses the runlength encoder program is shown next.

A runlength encoder testbench

 ipblock my_rle(in data : ns(8);
                out tuplenum : ns(8);
                out tupledata : ns(8)) {
   iptype "rle";
   ipparm "maxlen=32";
 }
 
 dp senddata(out data      : ns(8);
             in  tuplenum  : ns(8);
             in  tupledata : ns(8)) {
   lookup T : ns(8) = {1, 1, 1, 3, 4, 4, 6, 6, 6, 6};
   reg c : ns(8);
 
   always {
     c = (c == 9) ? 0 : c + 1;
     data = T(c);
     $display($cycle, ": ", data, " -> (", tuplenum, ", ", tupledata, ")");
   }
 }
 
 dp sysrle {
   sig i, tn, td : ns(8);
   use my_rle(i, tn, td);
   use senddata(i, tn, td);
 }
 
 system S {
   sysrle;
 }

The program generates the following output to confirm the correct operation of the runlength encoder.

 > fdlsim rle.fdl 15
 1:  1 -> (0, 0)
 2:  1 -> (0, 0)
 3:  1 -> (0, 0)
 4:  3 -> (3, 1)
 5:  4 -> (1, 3)
 6:  4 -> (0, 0)
 7:  6 -> (2, 4)
 8:  6 -> (0, 0)
 9:  6 -> (0, 0)
 10: 6 -> (0, 0)
 11: 1 -> (4, 6)
 12: 1 -> (0, 0)
 13: 1 -> (0, 0)
 14: 3 -> (3, 1)
 15: 4 -> (1, 3)
 Activity(%) on 1 registers: 100 (15/15)

Other member functions for aipblock

 virtual void aipblock::stop();

This function is called in gplatform (see Section 5.2 on page 41) when the simulation terminates.

 virtual void aipblock::probe(char *);

This is a probing function, to be used in combination with a cosimulation interface. It allows an external simulator (such as an ISS) to query specific GEZEL ipblocks. An example of a library block that calls this function is armsimprobe.