GreenScript User Manual

Copyright GreenSocs Ltd 2008

GreenScript User Manual v0.3

GreenScript User Manual v0.3

Chapter 1
Introduction

System-level hardware modeling becomes more important each day. A lot of effort is done nowadays to leverage the abstraction level and simplify the architecture exploration. The GreenScript project aims in this direction, by making legacy SystemC models to interoperate with higher abstraction level models done in scripting languages.
The motivation is to be able to explore the advantages of a dynamically-typed language with garbage-collection in the context of systems modeling, recognizing that model development time is often more important than model run time, especially for system architecture exploration. GreenScript simulations are expected to run much slower than SystemC simulations and this is considered acceptable. The idea is that GreenScript can be used when a model is needed very quickly or for coding parts of a model that are activated very infrequently and thus have no significant impact on the overall simulation speed.
The syntax provided to the user will remain as close as possible to SystemC, and semantics of events, threads, scheduling, delta cycles and so on should be the same in GreenScript and SystemC. The user interface is composed by classes and functions under the module gs.
At the time of this writing, only the Python interface is available. This project is in beta stage. There may be changes in the interfaces described in this document to accommodate particularities of other scripting languages like Perl, TCL, etc.
For examples how to use this interface, please take a look at the examples directory. To run, import the example inside python or use python example.py. The embedded examples have to be compiled and run outside Python.

Chapter 2
Using GreenScript

GreenScript can be used in two modes. One is the so-called standalone mode, where the system modules are implemented by Python scripts and the top-level file is also a Python script. This mode doesn't need a compilation step, as the scripts are directly run in the Python interpreter.
The other mode is called embedded mode, where the system designer is free to implement some parts of the system in Python and others in SystemC. The top-level file must be SystemC in this case. The C++ files are compiled into an executable binary and the Python scripts are dynamically loaded into the embedded Python interpreter.
Before going on detais on the GreenScript modes of operation, the next section shows how to do a piece of code in GreenScript. Following that the information how to use GreenScript in standalone mode or embedded mode.

2.1  Coding with GreenScript

The GreenScript framework does not enforce the user to create hardware modules in Python. A system can be made of group of threads without a hierarchy, for example. But the use of modules makes it possible to instantiate the same code more then once. The concept becomes closer to the way SystemC operates.
The example bellow shows a piece of code for a valid design in GreenScript. It is a thread to write to an output signal the double of what is read from an input signal, whenever this input signal changes. The code defines all resources in top-level, without any hierarchy.
import gs                # Load GreenScript definitions

# Global signals
input = gs.signal()
output = gs.signal()

# Global thread
def double():
    while(True):
        gs.wait(input.write_event())
        output.write(input.read() * 2)

gs.spawn(double)         # Start global thread

# Other thread should be defined to drive the input signal (...)

This last example suites well for small designs. For bigger systems, it may be better to group functionality in modules. This has some advantages, like better reusability and higher abstraction level.
The following example is equivalent to the last one, but now it embraces the thread in a class, thus defining a module. The module input and output can be parametrized by using module ports, so the module can be instanciated more then once when required. (note that it was impossible in the last example because all threads would change the same set of signals)
# Load GreenScript definitions
import gs

# Declare the module
class doubler:

    def double(self):
        while(True):
            gs.wait(self.input.write_event())
            self.output.write(self.input.read() * 2)

    def __init__(self, input=None, output=None):
        self.input = input
        self.output = output
        gs.spawn(self.double)

        
# Describe the system (in possibly other file)

s1 = gs.signal()
s2 = gs.signal()

# Instanciate module
# option 1: positional port binding
     my_doubler = doubler(s1, s2)
# option 2: named port binding
     my_doubler = doubler()
     my_doubler.input = s1
     my_doubler.output = s2

# Other module to drive input for doubler (...)

2.2  Standalone mode

The standalone mode means that the whole simulation runs inside the Python interpreter command-line. All files describing the system are Python scripts and thus they don't need a separate compilation step. Just start the Python interpeter and load the necessary files to run the simulation.
This was the first mode of operation available in GreenScript. It may be easier to use this mode when the user is starting to test GreenScript, or developing a small system. But for a more serious use, the embedded mode is recomended.

2.3  Embedded mode

The embedded mode can run the same scripts from the standalone mode, but normally some minor changes are done to allow the communication with the SystemC world. In this mode, GreenScript modules are loaded by the top-level SystemC file, inside de sc_main function.
In order to instanciate a GreenScript module, the designer first instanciates a container module, then load scripts to this container. Finaly, he instanciates the desired GreenScript module running a script expression. This is done in the following code:
#include <systemc.h>
#include <greenscript.h>
(...)

int sc_main(int argc, char *argv[])
{
  // Declare interfaces (signals, fifos, etc.)
  sc_signal<int> in1("in1"), in2("in2"), out1("out1");

  // Instanciate a container module to load Python scripts
  GreenScriptModule gs_module("gs_module");

  // Load desired scripts that defines modules
  gs_module.load("adder.py");

  // Instanciate Module (and do port binding)
  PyRun_SimpleString("my_adder = adder('in1','in2','out1')");

  //Instanciate other modules (either SystemC or GreenScript)
  //  to drive signals named in1 and in2.
  (...)

  // Run the simulation allowing Python threads (for GreenScript)
  Py_BEGIN_ALLOW_THREADS;
  sc_start(100);
  Py_END_ALLOW_THREADS;

  Py_Finalize();
  return 0;
}

Note that the interfaces (signals, fifos, etc.) that will be acessed in GreenScript must be named explicitly, or else SystemC will generate a random name for them and GreenScript will have no way to find them. The communication between SystemC and GreenScript in this last example is done with the signals named in1, in2 and out1. The first two are connected as input and the third as output to the adder GreenScript module. They can also be connected to SystemC modules in order to drive the inputs and read the output.
Only sc_signal < int > and sc_fifo < int > are recognized for now. More interfaces and user-defined interfaces are work for future GreenScript versions. The GreenScript module should use the classes gs.sc_signal and gs.sc_fifo to allow the communication with SystemC modules. Please refer to Chapter for more information on these classes.
The adder module is listed bellow:
import gs

class adder:
    
    def doit(self):
        while True:
            gs.wait(self.in1.write_event() | self.in2.write_event())
            self.out.write(self.in1.read() + self.in2.read())

    def __init__(self, in1, in2, out):
        self.in1 = gs.sc_signal(in1)
        self.in2 = gs.sc_signal(in2)
        self.out = gs.sc_signal(out)
        gs.spawn(self.doit)

Chapter 3
Python interface reference

Each script that will describe a part of the system should import the main GreenScript library module, using import gs. This defines the classes and functions described in the following sections.

3.1  Classes

gs.event
The event class. The objects os this class has a correspondent sc_event in the SystemC world.
It has 2 optional constructor arguments, mainly used internally. Without any argument, creates a new sc_event in the SystemC world and binds it to this new event. The first argument is a Python object that correspond to a sc_object, in this case the new event will be bind to the default event of this object. The second argument is a constant to select which event to bind to: gs.EVENT_CHANGED (the default), gs.EVENT_READ, gs.EVENT_WRITE.

Methods:
notify() Notify immediate
notify(0) Notify after one delta
notify(time, unit) Notify after time. The unit is an optional constant and can be one of gs.SC_FS, gs.SC_PS, gs.SC_NS (the default), gs.SC_US, gs.SC_MS, gs.SC_SEC.
wait() Wait for this event


gs.event_tree
This class cannot be directly instanciated. Objects os this class are created automatically when using event expressions with and and or operators, eg:
e1 = gs.event()
e2 = gs.event()
e3 = gs.event()
my_tree = (e1 and e2) or e3

Methods:
wait() Wait for this event tree expression


gs.primitive_channel
A base class for creating new primitive channels. The user may derive a class from the gs.primitive_channel and change the implementation of the class's update() method. Unlike SystemC, in GreenScript primitive channels can be created and deleted during the simulation run. The gs.signal and gs.fifo classes are examples of channels derived from this class.

Methods:
update() Empty method in this class. Should be re-defined in derived classes to model the needed channel. Should not be called by the user.


gs.signal
Derived from gs.primitive_channel. The gs.signal behaves like an sc_signal except that gs.signal can be instantiated and deleted during simulation run, not only at elaboration time.
It has one constructor argument, the initial value, which defaults to False.
A gs.signal can be set to any Python object and there is no restriction on the type, which can change during the simulation.
This class does not comunicate with SystemC directly. Use gs.sc_signal for this purpose.

Methods:
read() Returns the value
write(object) Stores any Python object as the signal value
write_event() Returns an gs.event triggered when this signal is written to


gs.sc_signal
The gs.sc_signal class wraps directly a SystemC sc_signal object. The corresponding sc_signal object is searched in the SystemC object hierarchy by it's name during inicialization, so the sc_signal must be declared earlier in the SystemC world.
This class can be instanciated and destructed during elaboration or simulation run, as long as the corresponding sc_signal is declared earlier and in elaboration time.
It has one constructor argument, the name of the sc_signal to capture. It differs from gs.signal because the initial value is not an argument for this class.
The type of the object that is written to the gs.sc_signal must be compatible with the original type of the sc_signal.

Methods:
read() Returns the value
write(object) Stores the object as the signal value, the type must be convertible to the internal sc_signal type
write_event() Returns an gs.event triggered when this signal is written to


gs.fifo
Derived from gs.primitive_channel. The gs.fifo behaves like an sc_fifo except that gs.fifo can be instantiated and deleted during simulation run, not only at elaboration time.
One constructor argument, the FIFO size, which defaults to 0, meaning infinite size.
A gs.fifo can contain up to "size" Python objects and there is no restriction on the object types or mixture of types.
This class does not comunicate with SystemC directly. Use gs.sc_fifo for this purpose.

Methods:
read() Blocking read. Remove and return the first value from the queue
nb_read() Non-blocking read. Returns a tuple (bool, value) where the first value shows success or failure of the read
peek() Blocking peek. Just return the first value from the queue without removing
nb_peek() Non-blocking peek. Returns a tuple (bool, value) where the first value shows success or failure of the peek
num_available() The number of objects in the queue
write(object) Blocking write. Stores any Python object in the end of the queue
nb_write(object) Non-blocking write. Returns a bool to indicate success or failure of the write
num_free() The number of free positions in the queue
write_event() Returns an gs.event triggered when this fifo is written to
read_event() Returns an gs.event triggered when this fifo is read from


gs.sc_fifo
The gs.sc_fifo class wraps directly a SystemC sc_fifo object. The corresponding sc_fifo object is searched in the SystemC object hierarchy by it's name during inicialization, so the sc_fifo must be declared earlier in the SystemC world.
This class can be instanciated and destructed during elaboration or simulation run, as long as the corresponding sc_fifo is declared earlier and in elaboration time.
It has one constructor argument, the name of the sc_fifo to capture. It differs from gs.fifo because the size of the queue is not a argument for this class.
The type of the object that is written to the gs.sc_fifo must be compatible with the original type of the sc_fifo.
It doesn't have peek and nb_peek like gs.fifo, because sc_fifo does not support these methods.

Methods:
read() Blocking read. Remove and return the first value from the queue
nb_read() Non-blocking read. Returns a tuple (bool, value) where the first value shows success or failure of the read
num_available() The number of objects in the queue
write(object) Blocking write. Stores the object in the end of the queue. The type must be convertible to the internal sc_fifo type
nb_write(object) Non-blocking write. Returns a bool to indicate success or failure of the write
num_free() The number of free positions in the queue
write_event() Returns an gs.event triggered when this fifo is written to
read_event() Returns an gs.event triggered when this fifo is read from


gs.spawn
The user creates processes in GreenScript using gs.spawn. There is no distinction between threads and methods (everything is a thread). Processes may be created at any time; during elaboration or during the simulation.
Constructor arguments: runnable, name, debug
runnable must be something the Python interpreter can "execute", eg. a function without arguments or an object of a class with an overwritten __run__ method (note that arguments can be added to a function using lambda in Python). name should be a string (it is optional, defaults to runnable.func_name). debug is a boolean, default False, that specifies that the runnable should be executed within the Python debugger pdb.
Known Issue: When more then one thread is debugged at the same time, the first one that finishes makes the rest of the threads execute until the end, as if there is no debug enabled in any other thread.

Methods:
reset() Cause the process to start again from the beginning, as soon as its current wait() returns
kill() Cause the process to stop and be deleted
pause() Cause the process to stall
resume() Cause a previously paused process to resume. Note that the process was in a wait() when it was paused and this wait() will in all cases complete as originally specified. There is no way to abort a wait().
(deprecated: continue is reserved word in Python, and cannot be used to name a method. This was renamed to resume)


gs.param
Create a configuration parameter. This uses GreenConfig to create parameters that are treated internally as strings, so config files are interpreted as strings for these parameters. In the Python world, this string is evaluated to a Python object by using eval().
One constructor argument, the name of the parameter.
Please see Chapter for more information.

Methods:
read() Use the eval() function to evaluate the string saved in the parameter as a Python object
write(object) Write the string representation of the object to the parameter, using str(object).


gs.av.output
This is part of GreenAV, the Analysis and Visibility project. One functionality of this project is to monitor some parameters for changes. This class creates an output descriptor that will contain the changes in the registered parameters along time.
Many types of output files are supported. Please consult the GreenAV users guide for more information
It has two constructor arguments: the first is the type of the output to be created (gs.av.NULL, gs.av.FILE, gs.av.STDOUT, gs.av.CSV or gs.av.SCV) and the second the name of the file to output.

Methods:
add(param_name) Add the given parameter (by name) to the list of parameters that are monitored for changes. When this parameter changes, the output will be updated.
add(gs_param_obj) Add the given parameter (by reference) to the list of parameters that are monitored for changes. When this parameter changes, the output will be updated.
add_all() Add all existing parameters to the list of parameters that are monitored for changes.
remove(param_name) Remove the given parameter (by name) from the list of parameters that are monitored for changes.
remove(gs_param_obj) Remove the given parameter (by reference) from the list of parameters that are monitored for changes.
pause() Pause the output. The parameters won't be monitored.
pause(time, unit) Pause the output for the given amount of time. time is a floating-point number and unit is an optional constant that can be one of gs.SC_FS, gs.SC_PS, gs.SC_NS (the default), gs.SC_US, gs.SC_MS, gs.SC_SEC.
pause(event) Pause the output until the given gs.event fires. Currently it does not accept gs.event_tree.
resume() Resume the output when it is paused. The parameters will be monitored and changes outputed again.


3.2  Functions

gs.simulation_time(unit)
Get the simulation time. The unit is an optional constant and can be one of gs.SC_FS, gs.SC_PS, gs.SC_NS (the default), gs.SC_US, gs.SC_MS, gs.SC_SEC.

gs.wait(time, unit)
Wait for a given time. time is a floating-point number and unit is an optional constant that can be one of gs.SC_FS, gs.SC_PS, gs.SC_NS (the default), gs.SC_US, gs.SC_MS, gs.SC_SEC. Only call from a process.

gs.wait(event)
Argument is a gs.event or a gs.event_tree. Returns when the event fires. Only call from a process.

gs.fork(runnable_list, wait_for=-1, kill=False, debug=False)
Allows the user to create multiple related processes in parallel, with a blocking semantic. Each runnable in the runnable_list is used to start a process. The number of processes to wait_for defaults to the length of the list if set to a negative value. When this number of processes have terminated, control returns to the calling process. The other processes are killed at that time if the kill argument is True. If debug is set to True, some debug messages are printed to tell when processes start and finish.
Only call from a process.

gs.start(time, unit)
Start the simulation and run it for a certain time or until no more events left. time is a floating-point number that defaults to 0, which means infinite time. unit is an optional constant that can be one of gs.SC_FS, gs.SC_PS, gs.SC_NS (the default), gs.SC_US, gs.SC_MS, gs.SC_SEC.
This function is ignored when GreenScript is embedded in a SystemC simulation. The sc_start() function takes precedence. Any Python code after the gs.start() call would in the embedded case execute immediately rather than after the end of the simulation.

gs.stop()
Stop the simulation. Only call from a process.

gs.create_thread(runnable, name)
Legacy support based on SystemC-2.0.1, which does not support dynamic creation of processes. Processes were created during elaboration using this function. This is just a wrapper to gs.spawn now.

gs.delta_count()
Return the delta cycle count.

gs.time(unit)
Show a message with simulation time and delta cycle count. unit is an optional constant that can be one of gs.SC_FS, gs.SC_PS, gs.SC_NS (the default), gs.SC_US, gs.SC_MS, gs.SC_SEC. The format of the output is time=XX (delta=YY).

Chapter 4
Using parameters with the configure framework

The configure framework used in GreenScript is provided by GreenControl. This chapter shows only the concepts that relates to GreenScript. For more information on GreenConfig, please consult the GreenControl Users's Manual.
The entry point to use parameters to configure a GreenScript module is to instanciate a gs.param class. The name of the parameter must be given as the constructor argument. For example:
my_param = gs.param("my_param")
other = gs.param("other")

In the current version of GreenScript and GreenConfig, the hierarchy of parameter names is not quite right, because GreenScript descriptions can be done non-hierarchical, while GreenConfig does not yet support parameters without a hierarchy. The parameters will be created inside a module called gs. This means that the full name of the above parameters, to be used in configuration input files, are gs.my_param and gs.other. As an alternative, the user can write the full name of the parameter in the constructor.
The value of the parameter is treated internally as a C++ std::string. When reading the parameter with the read() method, the internal string is evaluated to a Python object. If the parameter is not set, then the object read is the empty string. If the string is a valid integer, float, and so forth, the object returned will be of the evaluated type. It recognizes all the types that the build-in Python eval() function recognizes. If something goes wrong with eval(), the original string is returned.
When writing to a parameter with the write(object) method, the Python object given is first converted to string using str(object), then written to the internal std::string.
Other available functionality for parameter objects are:
  • applying str() to it returns the original std::string
  • it can be in boolean contexts, like if-statements (defines __nonzero__)
  • it can access itens with braces operator (proxies the __getitem__ function to the underlying object)
  • it can be converted to simple data types (defines __int__, __float__, __long__, __complex__)