Skip to content

Commit

Permalink
start to import dpi export functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Kuree committed Jul 21, 2024
1 parent ad3fcbe commit 69c0ff9
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 11 deletions.
2 changes: 1 addition & 1 deletion pysv/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .function import sv, is_run_function_set, set_run_function, make_call
from .function import sv, is_run_function_set, set_run_function, make_call, import_
from .types import DataType, Reference
from .codegen import generate_cxx_binding, generate_sv_binding
from .compile import compile_lib
Expand Down
43 changes: 42 additions & 1 deletion pysv/codegen.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Union, List
from .function import Function, DPIFunctionCall, sv
from .function import Function, DPIFunctionCall, sv, DPIImportFunction
from .types import DataType
from .model import check_class_ctor, get_dpi_functions, inject_destructor, check_class_method
from .util import (should_add_class, should_add_sys_path, make_dirs, make_unique_func_defs, should_import, is_conda,
Expand Down Expand Up @@ -55,6 +55,14 @@ def __should_include_buffer_impl(func_defs):
return False


def __should_generate_func_import(func_defs):
func_defs = __get_func_defs(func_defs)
for func_def in func_defs:
if isinstance(func_def, DPIImportFunction):
return True
return False


def __get_conda_path():
result = ""
if is_conda():
Expand Down Expand Up @@ -144,6 +152,8 @@ def generate_dpi_signature(func_def: Union[Function, DPIFunctionCall],

if is_class or is_function_class_wrapper:
dpi_str = "function"
elif isinstance(func_def, DPIImportFunction):
dpi_str = 'export "DPI-C" function'
else:
dpi_str = 'import "DPI-C" function'

Expand All @@ -167,6 +177,11 @@ def generate_dpi_signature(func_def: Union[Function, DPIFunctionCall],
func_name = func_def.base_name
else:
func_name = func_def.func_name

if isinstance(func_def, DPIImportFunction):
# dpi function does not need func type info
return dpi_str + " " + func_name + ";"

func_name += "("
result = " ".join([s for s in [dpi_str, return_type_str, func_name] if s])
if pretty_print:
Expand Down Expand Up @@ -513,6 +528,10 @@ def generate_check_interpreter():
def generate_cxx_function(func_def: Union[Function, DPIFunctionCall], pretty_print: bool = True,
add_sys_path: bool = True, add_class: bool = True):
result = get_c_function_signature(func_def, pretty_print)
if isinstance(func_def, DPIImportFunction):
# just need to produce a function declaration
result += ";\n"
return result
result += " {\n"
if add_sys_path:
result += generate_sys_path_check()
Expand Down Expand Up @@ -925,6 +944,20 @@ def generate_cxx_class(cls, pretty_print: bool = True, include_implementation: b
return result


def generate_pybind_function(func_defs: List[Union[type, DPIFunctionCall]], pretty_print: bool = True):
result = ""
code_blocks = []
for func_def in func_defs:
if isinstance(func_def, DPIImportFunction):
code_blocks.append('m.def("' + func_def.func_name + '"), &' + func_def.func_name + ");")
if code_blocks:
if pretty_print:
result = " " + "\n ".join(code_blocks) + "\n"
else:
result = "\n".join(code_blocks)
return result


def generate_cxx_binding(func_defs: List[Union[type, DPIFunctionCall]], pretty_print: bool = True,
filename=None, include_implementation: bool = False, namespace: str = "pysv"):
if filename is not None:
Expand All @@ -948,6 +981,10 @@ def generate_cxx_binding(func_defs: List[Union[type, DPIFunctionCall]], pretty_p
if not include_implementation:
result += generate_c_headers(func_defs, pretty_print)

add_func_import = __should_generate_func_import(func_defs)
if add_func_import:
result += "#include <pybind11/pybind11.h>\n"

# need to output namespace
result += "namespace {0} {{\n".format(namespace)

Expand All @@ -962,6 +999,10 @@ def generate_cxx_binding(func_defs: List[Union[type, DPIFunctionCall]], pretty_p
ref_ctor=func_def in class_refs)
result += generate_cxx_function_with_class(func_defs, include_implementation=include_implementation,
pretty_print=pretty_print)
if __should_generate_func_import(func_defs):
result += "PYBIND11_MODULE(" + namespace + ", m) {"
result += generate_pybind_function(func_defs, pretty_print=pretty_print)
result += "}\n"

# end of namespace
result += "}\n"
Expand Down
12 changes: 11 additions & 1 deletion pysv/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ def __init__(self, return_type: Union[DataType, type, Reference] = DataType.Int,

# check arg types
for t in arg_types.values():
print(type(t), t)
assert isinstance(t, (DataType, type))
for name, t in arg_types.items():
t = self.__check_arg_type(name, t)
Expand Down Expand Up @@ -165,6 +164,15 @@ def init_function_name():
sv = DPIFunction


class DPIImportFunction(DPIFunction):
def __init__(self, return_type: Union[DataType, type, Reference] = DataType.Int, **arg_types):
super().__init__(return_type, **arg_types)


# aliasing
import_ = DPIImportFunction


class DPIFunctionCall:
# all the functions will be run by default
# Python-based hardware generator needs to turn this off
Expand All @@ -188,6 +196,8 @@ def __get__(self, instance, owner):
return self

def __call__(self, *args, **kwargs):
if isinstance(self.func_def, DPIImportFunction):
raise ValueError("Imported DPI function cannot be called directly from Python")
if DPIFunctionCall.RUN_FUNCTION:
# need to be extra careful about class methods
if self.func_def.parent_class is not None:
Expand Down
7 changes: 6 additions & 1 deletion pysv/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,19 @@ def __enter__(self):


class VerilatorTester(Tester):
def __init__(self, lib_path, *files: str, cwd=None, clean_up_run=False):
def __init__(self, lib_path, *files: str, cwd=None, clean_up_run=False, flags=None):
super().__init__(lib_path, *files, cwd=cwd, clean_up_run=clean_up_run)
if not flags:
self.flags = []
else:
self.flags = flags

def run(self, blocking=True):
# compile it first
verilator = shutil.which("verilator")
args = [verilator, "--cc", "--exe"]
args += self.files + [os.path.abspath(self.lib_path), "-Wno-fatal"]
args += self.flags
subprocess.check_call(args, cwd=self.cwd)
# symbolic link it first
env = self._set_lib_env()
Expand Down
22 changes: 19 additions & 3 deletions tests/test_codegen.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pysv import sv, DataType, Reference
from pysv import sv, DataType, Reference, import_
from pysv.codegen import (get_python_src, generate_cxx_function, generate_c_header, generate_pybind_code,
generate_sv_binding, generate_cxx_binding, generate_dpi_signature)
generate_sv_binding, generate_cxx_binding, generate_dpi_signature, generate_pybind_function)
# all the module imports in this file should be local to avoid breaking assertions


Expand Down Expand Up @@ -59,6 +59,12 @@ def test_generate_dpi_header(check_file):
result = generate_dpi_signature(simple_func, pretty_print=False)
assert result == 'import "DPI-C" function int simple_func(input int a, input int b, input int c);'

@import_
def simple_func_import(a, b):
pass
result = generate_dpi_signature(simple_func_import, pretty_print=False)
assert result == 'export "DPI-C" function simple_func_import;'


class SomeClass:
def __init__(self):
Expand Down Expand Up @@ -90,9 +96,19 @@ def test_generate_cxx_binding(check_file):
result = generate_cxx_binding([SomeClass])
check_file(result, "test_generate_cxx_binding.cc")


def test_generate_cxx_code_class(check_file):
result = generate_pybind_code([SomeClass])
check_file(result, "test_generate_cxx_code_class.cc")


def test_generate_pybind_function():
@import_
def simple_func_import(a, b):
pass
result = generate_pybind_function([simple_func_import], pretty_print=False)
assert result == 'm.def("simple_func_import"), &simple_func_import);'


if __name__ == "__main__":
test_generate_c_header()
test_generate_pybind_function()
12 changes: 10 additions & 2 deletions tests/test_function.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from pysv import sv, DataType, is_run_function_set, set_run_function
from pysv import sv, DataType, is_run_function_set, set_run_function, import_


def test_frame_import():
Expand Down Expand Up @@ -116,5 +116,13 @@ def foo():
assert isinstance(foo, sv)


def test_import():
@import_
def foo(a):
pass

assert isinstance(foo, import_)


if __name__ == "__main__":
test_no_call_sv()
test_import()
29 changes: 27 additions & 2 deletions tests/test_sim.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pysv.util
from pysv import sv, compile_lib, DataType, generate_cxx_binding, generate_sv_binding, Reference
from pysv import sv, compile_lib, DataType, generate_cxx_binding, generate_sv_binding, Reference, import_
from importlib.util import find_spec
import pytest
import os
Expand Down Expand Up @@ -212,7 +212,32 @@ def set_value(a):
assert out == "2\n42\n"


@pytest.mark.skipif(not pysv.util.is_verilator_available(), reason="Verilator not available")
@pytest.mark.xfail(reason="cross python import not working yet")
def test_exrpot_dpi(get_vector_filename, temp):
@import_
def echo(a):
pass

@sv
def test():
a = echo(41)
print(a)

lib_path = compile_lib([test, echo], cwd=temp, lib_name="test_lib")
cxx_file = os.path.join(os.path.abspath(temp), "test_export_dpi.cc")
generate_cxx_binding([test, echo], filename=cxx_file)
sv_pkg = os.path.join(os.path.abspath(temp), "pysv_pkg.sv")
generate_sv_binding([test, echo], filename=sv_pkg, pkg_name="test_lib")
sv_file = get_vector_filename("test_export_dpi.sv")
tester = pysv.util.VerilatorTester(lib_path, sv_pkg, sv_file, cwd=temp, flags=["--main"])
out = tester.run().decode("ascii")
assert out == "2\n42\n"


if __name__ == "__main__":
import sys
sys.path.append("/home/keyi/workspace/pysv")
from conftest import get_vector_filename_fn
test_verilator_array(get_vector_filename_fn, "temp")
test_exrpot_dpi(get_vector_filename_fn, "temp")

10 changes: 10 additions & 0 deletions tests/vectors/test_export_dpi.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function int echo(int a);
return a + 1;
endfunction

module main;
initial begin
test_lib::test();
end

endmodule

0 comments on commit 69c0ff9

Please sign in to comment.