diff --git a/.gitmodules b/.gitmodules index 70d1e93..685f526 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "extern/pybind11"] path = pysv/extern/pybind11 url = https://github.com/pybind/pybind11 +[submodule "pysv/extern/vlstd"] + path = pysv/extern/vlstd + url = https://github.com/Kuree/vlstd diff --git a/pysv/CMakeLists.txt b/pysv/CMakeLists.txt index f7855a8..8c29aad 100644 --- a/pysv/CMakeLists.txt +++ b/pysv/CMakeLists.txt @@ -12,6 +12,9 @@ endif() target_link_libraries(${TARGET} PRIVATE pybind11::embed) +# include the sv lib directory +target_include_directories(${TARGET} PRIVATE ${DPI_HEADER_DIR}) + set(SHARED_LIB_EXT ".so") add_definitions(-DPYTHON_LIBRARY="${PYTHON_LIBRARY}") diff --git a/pysv/codegen.py b/pysv/codegen.py index e6b0652..0785145 100644 --- a/pysv/codegen.py +++ b/pysv/codegen.py @@ -45,6 +45,16 @@ def __should_include_local_object(func_defs): return False +def __should_include_buffer_impl(func_defs): + func_defs = __get_func_defs(func_defs) + for func_def in func_defs: + func_def = __get_func_def(func_def) + for input_type in func_def.arg_types.values(): + if input_type == DataType.IntArray: + return True + return False + + def __get_conda_path(): result = "" if is_conda(): @@ -58,6 +68,45 @@ def __get_conda_path(): return result +def __is_array(t: DataType): + return t == DataType.IntArray + +def __get_dpi_data_type(t: DataType): + if t == DataType.Bit: + return "bit" + elif t == DataType.Byte: + return "byte" + elif t == DataType.ShortInt: + return "shortint" + elif t == DataType.Int: + return "int" + elif t == DataType.LongInt: + return "longint" + elif t == DataType.UByte: + return "byte unsigned" + elif t == DataType.UShortInt: + return "shortint unsigned" + elif t == DataType.UInt: + return "int unsigned" + elif t == DataType.ULongInt: + return "longint unsigned" + elif t == DataType.Object: + return "chandle" + elif t == DataType.String: + return "string" + elif t == DataType.Float: + return "shortreal" + elif t == DataType.Double: + return "real" + # only for return type + elif t == DataType.Void: + return "void" + # the only array type supported + elif t == DataType.IntArray: + return "int" + raise ValueError("Unknown type") + + def generate_dpi_signature(func_def: Union[Function, DPIFunctionCall], pretty_print=True, is_class=False, is_function_class_wrapper=False, ref_ctor_name=""): @@ -78,14 +127,14 @@ def generate_dpi_signature(func_def: Union[Function, DPIFunctionCall], else: arg_type_str = __PYSV_OBJECT_BASE else: - arg_type_str = arg_type.value - args.append("input {0} {1}".format(arg_type_str, arg_name)) + arg_type_str = __get_dpi_data_type(arg_type) + args.append("input {0} {1}{2}".format(arg_type_str, arg_name, "[]" if __is_array(arg_type) else "")) # notice that we generate output at the end for arg_name in func_def.output_names: # we only allow primitive data types for return reference type arg_type = func_def.arg_types[arg_name] - arg_type_str = arg_type.value - args.append("output {0} {1}".format(arg_type_str, arg_name)) + arg_type_str = __get_dpi_data_type(arg_type) + args.append("output {0} {1}{2}".format(arg_type_str, arg_name, "[]" if __is_array(arg_type) else "")) # additional signature for ref ctor if is_class and len(ref_ctor_name) > 0 and func_def.is_init: @@ -104,7 +153,7 @@ def generate_dpi_signature(func_def: Union[Function, DPIFunctionCall], else: return_type_str = __PYSV_OBJECT_BASE else: - return_type_str = func_def.return_type.value + return_type_str = __get_dpi_data_type(func_def.return_type) if is_class: if func_def.is_init: @@ -268,6 +317,9 @@ def get_c_type_str(data_type: DataType): # pragma: no cover return "double" elif data_type == DataType.Void: return "void" + elif data_type == DataType.IntArray: + # dpi open aray type + return "svOpenArrayHandle" else: raise ValueError(data_type) @@ -359,6 +411,8 @@ def generate_local_variables(func_def: Union[Function, DPIFunctionCall]): arg_type = func_def.arg_types[n] if arg_type == DataType.Object and not (idx == 0 and func_def.parent_class is not None): s = __INDENTATION + __GET_LOCAL_OBJECT + '("{0}", {1}, locals);\n'.format(str_name, n) + elif __is_array(arg_type): + s = __INDENTATION + 'locals["{0}"] = to_buffer({1});\n'.format(str_name, n) else: s = __INDENTATION + 'locals["{0}"] = {1};\n'.format(str_name, n) result += s @@ -486,7 +540,7 @@ def generate_sys_path_values(pretty_print=True): def generate_bootstrap_code(pretty_print=True, add_sys_path=True, add_class=True, add_imports=True, - add_local_object=True): + add_local_object=True, add_buffer_impl=False): result = __get_code_snippet("include_header.hh") result += __get_code_snippet("runtime_values.cc") @@ -505,6 +559,8 @@ def generate_bootstrap_code(pretty_print=True, add_sys_path=True, add_class=True result += __get_code_snippet("import_global.cc") if add_local_object: result += __get_code_snippet("get_local_object.cc") + if add_buffer_impl: + result += __get_code_snippet("buffer_impl.cc") return result @@ -544,8 +600,10 @@ def generate_pybind_code(func_defs: List[Union[type, DPIFunctionCall]], pretty_p add_sys_path = should_add_sys_path(func_defs) add_imports = __has_imports(func_defs) add_local_object = __should_include_local_object(func_defs) + add_buffer_impl = __should_include_buffer_impl(func_defs) result = generate_bootstrap_code(pretty_print, add_sys_path=add_sys_path, add_class=add_class, - add_imports=add_imports, add_local_object=add_local_object) + "\n" + add_imports=add_imports, add_local_object=add_local_object, + add_buffer_impl=add_buffer_impl) + "\n" # generate extern C block result += 'extern "C" {\n' code_blocks = [] diff --git a/pysv/compile.py b/pysv/compile.py index 3f49b11..d76f424 100644 --- a/pysv/compile.py +++ b/pysv/compile.py @@ -22,6 +22,7 @@ def compile_lib(func_defs, cwd, lib_name="pysv", pretty_print=True, release_buil # need to copy stuff over root_dir = os.path.dirname(__file__) pybind_path = os.path.join(root_dir, "extern", "pybind11") + vlstd_path = os.path.join(root_dir, "extern", "vlstd") assert os.path.isdir(pybind_path) # copy that to cwd if it doesn't exist pybind_path_dst = os.path.join(cwd, "pybind11") @@ -58,6 +59,8 @@ def compile_lib(func_defs, cwd, lib_name="pysv", pretty_print=True, release_buil else: build_type = "Debug" cmake_args.append("-DCMAKE_BUILD_TYPE=" + build_type) + # add header definition + cmake_args.append("-DDPI_HEADER_DIR=" + vlstd_path) subprocess.check_call(["cmake"] + cmake_args + [".."], cwd=build_dir) # built it! diff --git a/pysv/extern/vlstd b/pysv/extern/vlstd new file mode 160000 index 0000000..c15fc09 --- /dev/null +++ b/pysv/extern/vlstd @@ -0,0 +1 @@ +Subproject commit c15fc091f5120704f4f7eccb19a78300eb663da0 diff --git a/pysv/function.py b/pysv/function.py index b78ccff..8649ed1 100644 --- a/pysv/function.py +++ b/pysv/function.py @@ -67,6 +67,7 @@ def __init__(self, return_type: Union[DataType, type, Reference] = DataType.Int, self.output_names.append(arg_name) self.arg_types[arg_name] = arg_type assert isinstance(self.return_type, DataType), "Return type has to be of " + DataType.__name__ + assert self.return_type != DataType.IntArray, "Returning an array is not supported" if imports is None: self.imports = _inspect_frame() else: diff --git a/pysv/snippets/buffer_impl.cc b/pysv/snippets/buffer_impl.cc new file mode 100644 index 0000000..fa67163 --- /dev/null +++ b/pysv/snippets/buffer_impl.cc @@ -0,0 +1,34 @@ +#include "svdpi.h" + +py::memoryview to_buffer(const svOpenArrayHandle array_handle) { + ssize_t element_size = sizeof(int32_t); + void *base_ptr = nullptr; + std::vector sizes = {}; + std::vector strides = {}; + + // we try to query the underlying representation + base_ptr = svGetArrayPtr(array_handle); + if (!base_ptr) { + throw std::runtime_error("Array type does not have native C representation"); + } + auto dim = svDimensions(array_handle); + for (auto i = 0; i < dim; i++) { + auto s = svSize(array_handle, i); + sizes.emplace_back(s); + } + // assumes row major ordering + ssize_t stride = element_size; + strides = std::vector(dim, element_size); + for (int i = 0; i < dim - 1; i++) { + stride *= sizes[i]; + strides[i] = stride; + } + + return py::memoryview::from_buffer( + base_ptr, /* Pointer to buffer */ + element_size, /* Size of one scalar */ + py::format_descriptor::value, /* Python struct-style format descriptor */ + sizes, /* Buffer dimensions */ + strides /* Strides (in bytes) for each index */ + ); +} diff --git a/pysv/types.py b/pysv/types.py index 036d995..b859c62 100644 --- a/pysv/types.py +++ b/pysv/types.py @@ -2,21 +2,23 @@ class DataType(enum.Enum): - Bit = "bit" - Byte = "byte" - ShortInt = "shortint" - Int = "int" - LongInt = "longint" - UByte = "byte unsigned" - UShortInt = "shortint unsigned" - UInt = "int unsigned" - ULongInt = "longint unsigned" - Object = "chandle" - String = "string" - Float = "shortreal" - Double = "real" + Bit = enum.auto() + Byte = enum.auto() + ShortInt = enum.auto() + Int = enum.auto() + LongInt = enum.auto() + UByte = enum.auto() + UShortInt = enum.auto() + UInt = enum.auto() + ULongInt = enum.auto() + Object = enum.auto() + String = enum.auto() + Float = enum.auto() + Double = enum.auto() # only for return type - Void = "void" + Void = enum.auto() + # the only array type supported + IntArray = enum.auto() class Reference: diff --git a/pysv/util.py b/pysv/util.py index 0373791..069e6a5 100644 --- a/pysv/util.py +++ b/pysv/util.py @@ -172,7 +172,7 @@ def _set_lib_env(self): def _run(self, args, cwd, env, blocking): if blocking: - subprocess.check_call(args, cwd=cwd, env=env) + return subprocess.check_output(args, cwd=cwd, env=env) else: p = subprocess.Popen(args, cwd=cwd, env=env) self.__process.append(p) @@ -218,7 +218,7 @@ def run(self, blocking=True): cwd=self.cwd, env=env) # run the application name = os.path.join("obj_dir", mk_file.replace(".mk", "")) - self._run([name], self.cwd, env, blocking) + return self._run([name], self.cwd, env, blocking) class CadenceTester(Tester): diff --git a/tests/test_compile.py b/tests/test_compile.py index 8a7d57e..b3b15fd 100644 --- a/tests/test_compile.py +++ b/tests/test_compile.py @@ -218,5 +218,16 @@ def func_output(): assert values[0] == 42 and values[1] == 43; +def test_buffer_int(temp): + @sv(return_type=DataType.Void, a=DataType.IntArray) + def foo(a): + a[0] = 2 + + # because we lack of dpi implementaiotn, we only test out + # and see if we can compile it + lib_file = compile_lib([foo], cwd=temp) + assert os.path.exists(lib_file) + + if __name__ == "__main__": - test_float("temp") + test_buffer_int("temp") diff --git a/tests/test_sim.py b/tests/test_sim.py index 954e87f..8d20528 100644 --- a/tests/test_sim.py +++ b/tests/test_sim.py @@ -189,7 +189,30 @@ def set_value(): tester.run() +@pytest.mark.skipif(not pysv.util.is_verilator_available(), reason="Verilator not available") +def test_verilator_array(get_vector_filename, temp): + @sv(a=DataType.IntArray) + def set_value(a): + print(a[2]) + a[2] = 42 + + lib_path = compile_lib([set_value], cwd=temp) + header_file = os.path.join(os.path.abspath(temp), "test_verilator_array.hh") + generate_cxx_binding([set_value], filename=header_file) + sv_pkg = os.path.join(os.path.abspath(temp), "pysv_pkg.sv") + generate_sv_binding([set_value], filename=sv_pkg) + + # we have three files + # the sv file, the driver file, and the header + sv_file = get_vector_filename("test_verilator_array.sv") + driver = get_vector_filename("test_verilator_array.cc") + # just run the verilator + tester = pysv.util.VerilatorTester(lib_path, sv_file, header_file, driver, cwd=temp) + out = tester.run().decode("ascii") + assert out == "2\n42\n" + + if __name__ == "__main__": from conftest import get_vector_filename_fn - test_verilator_return_reference(get_vector_filename_fn, "temp") + test_verilator_array(get_vector_filename_fn, "temp") diff --git a/tests/vectors/test_verilator_array.cc b/tests/vectors/test_verilator_array.cc new file mode 100644 index 0000000..b605203 --- /dev/null +++ b/tests/vectors/test_verilator_array.cc @@ -0,0 +1,13 @@ +#include "Vtest_verilator_array.h" +#include "test_verilator_array.hh" +#include +#include +#include + +int main () { + Vtest_verilator_array vtop; + vtop.eval(); + + // tear down the runtime + pysv_finalize(); +} diff --git a/tests/vectors/test_verilator_array.sv b/tests/vectors/test_verilator_array.sv new file mode 100644 index 0000000..36c0cef --- /dev/null +++ b/tests/vectors/test_verilator_array.sv @@ -0,0 +1,17 @@ +`include "pysv_pkg.sv" + +module test_verilator_array(); + +import pysv::*; + +int a[3:0]; + +initial begin + for (int i = 0; i < 4; i++) begin + a[i] = 2; + end + set_value(a); + $display("%0d", a[2]); +end + +endmodule