Skip to content

Latest commit

 

History

History
293 lines (191 loc) · 9.29 KB

basics.rst

File metadata and controls

293 lines (191 loc) · 9.29 KB

Basic usages

This section demonstrates the basic features of pysv.

Hello World!

Here is a simple yet complete example to execute some hello-world code from Python

from pysv import sv, compile_lib, generate_sv_binding

@sv()
def hello_world():
    print("hello world!")

# compile the a shared_lib into build folder
lib_path = compile_lib([hello_world], cwd="build")
# generate SV binding
generate_sv_binding([hello_world], filename="pysv_pkg.sv")

Now we can use the function in our test bench:

module top;
// import from the pysv package
import pysv::hello_world;

initial begin
    hello_world();
end
endmodule

To run it with Xcelium we can simply do

xrun pysv_pkg.sv top.sv -sv_lib build/libpysv.so

And it will print out helo world!. Notice that build/libpysv.so can be obtained from lib_path.

Function arguments and return types

By default, if a function argument type is not specified, pysv will assume it is of DataType.Int type (int32_t in C). To specify the argument type, we can add the types to the function decorator with given argument name.

from pysv import sv, DataType

@sv(s=DataType.String)
def print_dup_s(s, repeat):
    print(s * repeat)

In the example above, we specify the type of argument s to be string. Since repeat is an integer, we leave it unspecified to use default typing.

To specify return type, simply use keyword return_type, as shown below:

from pysv import sv, DataType

@sv(return_type=DataType.String)
def get_str():
    return "Hello World"

pysv uses two rules to infer return types if not specified

1. If the function body does not have any return statement, then it's default to DataType.Void. 2. Otherwise it is set to DataType.Int

Note

For more complex data type such as Python objects, please refer to :doc:`Object-Oriented Programming <advanced/oop>`.

If you wish to return multiple objects (i.e. tuple), due to the restriction of DPI typing system, we need to specify the return tuple using Reference object. Here is a simple example:

from pysv import sv, DataType, Reference

@sv(return_type=Reference(a=DataType.UInt, b=DataType.Uint))
def set_values():
    return 42, 43

In the example, we create a reference object to specify the return type names and their individual types. pysv will unwrap the tuple and set the output accordingly. In SystemVerilog, we will see the following function definition

function set_values(output int a, output int b);

Whereas in C/C++ we will see the following function definition:

void set_values(int *a, int *b);

Notice that your function can take normal input arguments. All the output arguments will be generated after the inputs.

If we want to pass a SystemVerilog open array to Python and used in libraries such as numpy, you can use DataType.IntArray. Currently only int32_t type is supported. Because it uses py::memoryview under the hood, the array will not be copied. Instead, it is accessed via a multable array view. As a result, the system can handle arbitrary number of dimensions.

To use a multidimensional array, You can specify the number dimension using the following syntax:

DataType.IntArray[4]

which produces a 4-D array. To use it in python, you need to use tuple-based indexing, e.g. array[2, 3, 4, 5]. Unfortunately CPython does not implement slicing of a subview. If modifying the array is not required, you can convert the array into a numpy array via

numpy_array = numpy.array(array)

However, doing so makes a copy of the array and you need to write back the changes.

Library compilation

In order to use pysv in your testbench, you first need to compile the python code into a native shared object, which can be linked into any supported simulators. To do so, simply call the function compile_lib:

lib_path = compile_lib(func_defs, cwd)

compile_lib returns the path to compiled shared object. cwd specifies the working directory of staged compilation. You can re-use the same cwd if you wish the speed up the compilation speed.

There are more optional arguments provided with default values:

  • lib_name: library name. Default value is "pysv". You will see generated shared library in the form of lib${lib_name}.so.
  • release_build: whether to use CMake release build. Default is False. Using release build will significantly improve the C++-Python interface performance, at the cost of prolonged compilation time.
  • clean_up_build: whether to remove the build folder. Notice that pysv creates a build folder for CMake to compile. Set this option to True to remove that folder. This, however, does not remove the immediate code generated.
  • add_sys_path: whether to add system path. Default is False. pysv uses a set of rules to detect whether user has imported a foreign module, and automatically set set system path if detected. However, should the rules fail, user can manually set this flag to True to force add system path.

Generate binding code

pysv provides ability to generate both SystemVerilog and C++ bindings. For object-oriented code generation, please refer to :doc:`Object-Oriented Programming <advanced/oop>`.

SystemVerilog binding

generate_sv_binding is the function you need to generate the SystemVerilog binding. Below is an example usage:

binding = generate_sv_binding([hello_world])

The first argument takes in a list of function names that's been decorated with @sv. An exception will thrown if pysv detects that the function has not been done so.

There are some optional arguments provided with default values:

  • pkg_name: the SystemVerilog package name. If not set, pysv is used.
  • pretty_print: whether to format the code based on some coding style. Default is True.
  • filename: if provided, pysv will write the binding code to the specified filename.

Note

generate_sv_binding always returns the string content of the binding, regardless whether the binding has been written to a file or not.

Below is the generated SystemVerilog function signature with our hello world example:

function void hello_world();

C++ binding

generate_cxx_binding is the function you need to generate the C++ binding. Below is an example uage:

binding = generate_cxx_binding([hello_world])

The first argument takes in a list of function names that's been decorated with @sv. An exception will thrown if pysv detects that the function has not been done so.

There are some optional arguments provided with default values:

  • namespace: the C++ namespace name. If not set, pysv is used.
  • pretty_print: whether to format the code based on some coding style. Default is True.
  • filename: if provided, pysv will write the binding code to the specified filename.
  • include_implementation: if set to True, the actual C++ implemented will be generated as well. Only for debugging, since the functions are not declared as inline and will likely trigger a linker error.

Note

generate_cxx_binding always returns the string content of the binding, regardless whether the binding has been written to a file or not.

Below is the generated C++ function signature with our hello world example:

void hello_world();

Import foreign modules

pysv can automatically detect any foreign module being used in the current working scope and modify the system path which python interpreter uses to search modules accordingly.

Currently supported import semantics:

  • Python modules, e.g.:
    import numpy
    import tensorflow as tf
  • Python classes from a module, e.g.:

    from tensorflow import Tensor
  • Python functions from a module, e.g.:

    from numpy import min

Due to the current implementation limitation, however, functions or classes created from local scope are not supported and exception will be thrown when pysv detects that. One workaround is to create or import such functions/class inside the decorated function.

Shutdown the Python runtime

pysv maintains several global state to ensure the liveness of Python objects. These global data structures need to be destroyed before simulation finishes, otherwise you may get a segfault depends on how simulator frees up memory. pysv generates pysv_finalize() function in SystemVerilog and C++ binding code so users can call it at the end of simulation.

Warning

For a small-scale simulation, especially with Verilator or no foreign modules are imported, ending simulation without calling pysv_finalize() will be fine in most cases. However, it is the best practice to call it at the end of simulation.