diff --git a/Agents.md b/Agents.md new file mode 100644 index 0000000..13d8c33 --- /dev/null +++ b/Agents.md @@ -0,0 +1,25 @@ + +# Initializing the Project +After the project is cloned, you must fetch external packages. +Use the following command to do this. Only do this if the 'packages' +directory is not present: + +``` +% ivpm update +``` + +# Building the Project +There are two steps required to build the native components of +the project: + +``` +% ./packages/python/bin/python setup.py build_ext --inplace +% ./packages/python/bin/python -m build -n . +``` + +# Running Tests +Unit tests can be run using the following command: + +``` +% PYTHONPATH=$(pwd)/src ./packages/python/bin/pytest -s tests/unit +``` \ No newline at end of file diff --git a/scripts/gen_py_if.py b/scripts/gen_py_if.py index 949425f..3bcecaf 100755 --- a/scripts/gen_py_if.py +++ b/scripts/gen_py_if.py @@ -855,7 +855,9 @@ def main(): args = parser.parse_args() - include_pref = {"Py_", "PyDict_", "PyEval_", "PyErr_", "PyImport_", + include_pref = {"Py_", "PyDict_", "PyEval_", "PyErr_", + "PyFloat_", + "PyImport_", "PyIter_", "PyList_", "PyLong_", "PyObject_", "PySet_", "PyTuple_", "PyUnicode_", "PyGILState_"} diff --git a/src/hdl_if/impl/call/call_proxy_dpi.py b/src/hdl_if/impl/call/call_proxy_dpi.py index 8e66340..f50cb6b 100644 --- a/src/hdl_if/impl/call/call_proxy_dpi.py +++ b/src/hdl_if/impl/call/call_proxy_dpi.py @@ -33,6 +33,7 @@ def invoke_hdl_f( self, method_name : str, args : tuple): + return self.ep.invoke_hdl_f( self.obj_id, method_name, diff --git a/src/hdl_if/impl/call/gen_sv_class.py b/src/hdl_if/impl/call/gen_sv_class.py index 8c41e7a..ae8bf84 100644 --- a/src/hdl_if/impl/call/gen_sv_class.py +++ b/src/hdl_if/impl/call/gen_sv_class.py @@ -358,6 +358,9 @@ def gen_tf_impl(self, m : MethodDef): pass else: self.println("pyhdl_if::PyObject __res;") + if m.rtype is not None: + self.println("%s __ret;" % self.svtype(m.rtype)) + self.println("pyhdl_if::PyGILState_STATE state = pyhdl_if::PyGILState_Ensure();") self.println("pyhdl_if::PyObject __args = pyhdl_if::PyTuple_New(%d);" % len(m.params)) for i,p in enumerate(m.params): self.println("void'(pyhdl_if::PyTuple_SetItem(__args, %d, %s));" % ( @@ -366,10 +369,17 @@ def gen_tf_impl(self, m : MethodDef): if m.kind == MethodKind.ExpTask: self.println("pyhdl_if::pyhdl_if_invokePyTask(__res, m_obj, \"%s\", __args);" % ( m.name,)) + self.println("pyhdl_if::PyGILState_Release(state);") else: self.println("__res = pyhdl_if::pyhdl_if_invokePyFunc(m_obj, \"%s\", __args);" % ( m.name,)) - self.println("return %s(__res);" % self.py2sv_func(m.rtype)) + if m.rtype is not None: + self.println("__ret = %s(__res);" % self.py2sv_func(m.rtype)) + + self.println("pyhdl_if::PyGILState_Release(state);") + + if m.rtype is not None: + self.println("return __ret;") self.dec_ind() @@ -454,6 +464,7 @@ def gen_func_dispatch(self, api): self.println("virtual function pyhdl_if::PyObject invokeFunc(string method, pyhdl_if::PyObject args);") self.inc_ind() self.println("pyhdl_if::PyObject __ret = pyhdl_if::None;") + self.println("pyhdl_if::PyGILState_STATE state = pyhdl_if::PyGILState_Ensure();") self.println("case (method)") self.inc_ind() for m in api.methods: @@ -500,21 +511,24 @@ def gen_func_dispatch(self, api): self.dec_ind() self.println("endcase") self.println() + self.println("pyhdl_if::PyGILState_Release(state);") self.println("return __ret;") self.dec_ind() self.println("endfunction") def py2sv_func(self, type): type_m = { - ctypes.c_bool : "PyLong_AsLong", + ctypes.c_bool : "py_as_bool", ctypes.c_byte : "PyLong_AsLong", ctypes.c_char : "PyLong_AsLong", - ctypes.c_double : "real", + ctypes.c_double : "py_as_double", + float : "py_as_double", ctypes.c_int : "PyLong_AsLong", ctypes.c_int8 : "PyLong_AsLong", ctypes.c_int16 : "PyLong_AsLong", ctypes.c_int32 : "PyLong_AsLong", ctypes.c_int64 : "PyLong_AsLongLong", + int : "PyLong_AsLongLong", ctypes.c_uint8 : "PyLong_AsLong", ctypes.c_uint16 : "PyLong_AsLong", ctypes.c_uint32 : "PyLong_AsLong", @@ -531,12 +545,14 @@ def sv2py_func(self, type, var): ctypes.c_bool : "PyLong_FromLong", ctypes.c_byte : "PyLong_FromLong", ctypes.c_char : "PyLong_FromLong", - ctypes.c_double : "real", + ctypes.c_double : "PyFloat_FromDouble", + float : "PyFloat_FromDouble", ctypes.c_int : "PyLong_FromLong", ctypes.c_int8 : "PyLong_FromLong", ctypes.c_int16 : "PyLong_FromLong", ctypes.c_int32 : "PyLong_FromLong", ctypes.c_int64 : "PyLong_FromLongLong", + int : "PyLong_FromLongLong", ctypes.c_uint8 : "PyLong_FromLong", ctypes.c_uint16 : "PyLong_FromLong", ctypes.c_uint32 : "PyLong_FromLong", @@ -556,11 +572,13 @@ def svtype(self, type): ctypes.c_byte : "byte", ctypes.c_char : "byte", ctypes.c_double : "real", + float : "real", ctypes.c_int : "int", ctypes.c_int8 : "byte", ctypes.c_int16 : "shortint", ctypes.c_int32 : "int", ctypes.c_int64 : "longint", + int : "longint", ctypes.c_uint8 : "byte unsigned", ctypes.c_uint16 : "shortint unsigned", ctypes.c_uint32 : "int unsigned", diff --git a/src/hdl_if/impl/call/imp_func_impl.py b/src/hdl_if/impl/call/imp_func_impl.py index 7e777bd..fa7743c 100644 --- a/src/hdl_if/impl/call/imp_func_impl.py +++ b/src/hdl_if/impl/call/imp_func_impl.py @@ -36,7 +36,6 @@ def __call__(self, api_self, *args, **kwargs): proxy : CallProxy = getattr(api_self, "_proxy") - print("args: %s" % str(args), flush=True) return proxy.invoke_hdl_f( self._md.name, args diff --git a/src/hdl_if/share/dpi/py_utils.svh b/src/hdl_if/share/dpi/py_utils.svh index 834e1a2..0bb7b5e 100644 --- a/src/hdl_if/share/dpi/py_utils.svh +++ b/src/hdl_if/share/dpi/py_utils.svh @@ -43,6 +43,44 @@ function automatic py_object py_from_str(string str); return ret; endfunction +function automatic string py_as_str(PyObject hndl); + string ret = PyUnicode_AsUTF8(hndl); + return ret; +endfunction + +function automatic bit py_as_bool(PyObject hndl); + longint ret = PyLong_AsLong(hndl); + + if (ret == -1 && PyErr_Occurred() != null) begin + PyErr_Print(); + end + + return (ret != 0); +endfunction + +function automatic longint py_as_long(PyObject hndl); + longint ret = PyLong_AsLong(hndl); + + if (ret == -1 && PyErr_Occurred() != null) begin + PyErr_Print(); + end + + return ret; +endfunction + +function automatic real py_as_double(PyObject hndl); + real ret = 0.0; + + ret = PyFloat_AsDouble(hndl); + if (ret == -1 && PyErr_Occurred() != null) begin + $display("Error occurred"); + PyErr_Print(); + end + + return ret; +endfunction + + /** * Import a module */ @@ -66,6 +104,8 @@ function automatic py_object py_call_builtin(string name, py_tuple args, py_dict PyObject ret_o; py_object ret = null; + `PYHDL_IF_ENTER(("py_call_builtin %0s", name)); + Py_IncRef(args.obj); builtins = PyEval_GetBuiltins(); Py_IncRef(builtins); @@ -90,26 +130,19 @@ function automatic py_object py_call_builtin(string name, py_tuple args, py_dict return null; end - - $display("--> PyObject_Call"); - $display("func: %0p", func); - $display("args: %0p", args.obj); - $display("kwargs: %0p", kwargs.obj); ret_o = PyObject_Call( - func, - args.obj, + func, + args.obj, kwargs.obj); - $display("<-- PyObject_Call"); - - $display("ret_o=%0p", ret_o); if (ret_o == null) begin - $display("ret_o is null"); PyErr_Print(); end else begin ret = new(ret_o); end + `PYHDL_IF_LEAVE(("py_call_builtin %0s", name)); + return ret; endfunction diff --git a/src/hdl_if/share/dpi/pyhdl_if.sv b/src/hdl_if/share/dpi/pyhdl_if.sv index 44af026..2cab9fc 100644 --- a/src/hdl_if/share/dpi/pyhdl_if.sv +++ b/src/hdl_if/share/dpi/pyhdl_if.sv @@ -138,7 +138,9 @@ package pyhdl_if; function automatic PyObject pyhdl_pi_if_HandleErr(PyObject obj); if (obj == null) begin + $display("--> HandleErr"); PyErr_Print(); + $display("<-- HandleErr"); end return obj; endfunction diff --git a/tests/unit/data/datatypes/datatypes_int_imp.py b/tests/unit/data/datatypes/datatypes_int_imp.py new file mode 100644 index 0000000..cba8c19 --- /dev/null +++ b/tests/unit/data/datatypes/datatypes_int_imp.py @@ -0,0 +1,24 @@ +import ctypes as ct +import hdl_if as hif + +@hif.api +class IntImp(object): + + @hif.imp + def add(self, a : ct.c_int, b : ct.c_int) -> ct.c_int: pass + + @hif.exp + def test(self) -> ct.c_int: + status = 0 + + try: + for i in range(10): + for j in range(10): + result = self.add(i, j) + + if result != i+j: + status = 1 + except Exception as e: + print("Exception: %s" % str(e), flush=True) + + return status diff --git a/tests/unit/data/datatypes/datatypes_int_imp.sv b/tests/unit/data/datatypes/datatypes_int_imp.sv new file mode 100644 index 0000000..6d696ec --- /dev/null +++ b/tests/unit/data/datatypes/datatypes_int_imp.sv @@ -0,0 +1,31 @@ +package datatypes_int_imp; + import datatypes_int_imp_pkg::*; + + class IntImpImpl extends IntImp; + function int add(int a, int b); + return a+b; + endfunction + endclass +endpackage + +module top; + import pyhdl_if::*; + import datatypes_int_imp::*; + + initial begin + automatic IntImpImpl int_imp = new(); + automatic int status = 0, fp; + + $display("Hello World!"); + status = int_imp.test(); + + fp = $fopen("status.txt", "w"); + if (status == 0) begin + $fwrite(fp, "PASS:\n"); + end else begin + $fwrite(fp, "FAIL:\n"); + end + $fclose(fp); + $finish; + end +endmodule diff --git a/tests/unit/data/datatypes/datatypes_real_exp.py b/tests/unit/data/datatypes/datatypes_real_exp.py new file mode 100644 index 0000000..59a8414 --- /dev/null +++ b/tests/unit/data/datatypes/datatypes_real_exp.py @@ -0,0 +1,11 @@ + +import ctypes as ct +import hdl_if as hif + +@hif.api +class RealExp(object): + + @hif.exp + def add(self, a : ct.c_double, b : ct.c_double) -> ct.c_double: + ret = a + b + return ret diff --git a/tests/unit/data/datatypes/datatypes_real_exp.sv b/tests/unit/data/datatypes/datatypes_real_exp.sv new file mode 100644 index 0000000..d6bd893 --- /dev/null +++ b/tests/unit/data/datatypes/datatypes_real_exp.sv @@ -0,0 +1,37 @@ + +package datatypes_real_exp; +endpackage + + +module top; + import pyhdl_if::*; + import datatypes_real_exp_pkg::*; + + initial begin + automatic RealExp real_exp = new(); + automatic int i, j; + automatic real result; + automatic int status = 0, fp; + + for (i=0; i<10; i++) begin + for (j=0; j<10; j++) begin + result = real_exp.add(real'(i), real'(j)); + if (result != (real'(i) + real'(j))) begin + $display("Error: %0d+%0d: expect %0f ; receive %0f", + i, j, (real'(i) + real'(j)), result); + status = 1; + end + end + end + + fp = $fopen("status.txt", "w"); + if (status == 0) begin + $fwrite(fp, "PASS:\n"); + end else begin + $fwrite(fp, "FAIL:\n"); + end + $fclose(fp); + $finish; + end +endmodule + diff --git a/tests/unit/data/datatypes/datatypes_real_imp.py b/tests/unit/data/datatypes/datatypes_real_imp.py new file mode 100644 index 0000000..1c52c7b --- /dev/null +++ b/tests/unit/data/datatypes/datatypes_real_imp.py @@ -0,0 +1,26 @@ + +import ctypes as ct +import hdl_if as hif + +@hif.api +class RealImp(object): + + @hif.imp + def add(self, a : ct.c_double, b : ct.c_double) -> ct.c_double: pass + + @hif.exp + def test(self) -> ct.c_int: + status = 0 + + try: + result = self.add(0, 0) + # for i in range(10): + # for j in range(10): + # result = self.add(i, j) + + # if result != i+j: + # status = 1 + except Exception as e: + print("Exception: %s" % str(e), flush=True) + + return status diff --git a/tests/unit/data/datatypes/datatypes_real_imp.sv b/tests/unit/data/datatypes/datatypes_real_imp.sv new file mode 100644 index 0000000..5abc3b2 --- /dev/null +++ b/tests/unit/data/datatypes/datatypes_real_imp.sv @@ -0,0 +1,34 @@ + +package datatypes_real_imp; + import datatypes_real_imp_pkg::*; + + class RealImpImpl extends RealImp; + function real add(real a, real b); + return a+b; + endfunction + endclass +endpackage + + +module top; + import pyhdl_if::*; + import datatypes_real_imp::*; + + initial begin + automatic RealImpImpl real_imp = new(); + automatic int status = 0, fp; + + $display("Hello World!"); + status = real_imp.test(); + + fp = $fopen("status.txt", "w"); + if (status == 0) begin + $fwrite(fp, "PASS:\n"); + end else begin + $fwrite(fp, "FAIL:\n"); + end + $fclose(fp); + $finish; + end +endmodule + diff --git a/tests/unit/test_datatypes.py b/tests/unit/test_datatypes.py new file mode 100644 index 0000000..5ea5cbc --- /dev/null +++ b/tests/unit/test_datatypes.py @@ -0,0 +1,132 @@ +import os +import sys +import pytest +from .test_base import * +from dv_flow.libhdlsim.pytest import hdlsim_dvflow, HdlSimDvFlow +from . import hdl_if_env, available_sims_dpi + + +data_dir = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data") +test_data_dir = os.path.join(data_dir, "datatypes") + +@pytest.mark.parametrize("hdlsim_dvflow", available_sims_dpi(), indirect=True) +def test_real_exp(hdlsim_dvflow, hdl_if_env): + env = hdl_if_env + env["PYTHONPATH"] = test_data_dir + os.pathsep + env["PYTHONPATH"] + hdlsim_dvflow.setEnv(env) + + print("test_smoke_data_dir: %s" % test_data_dir, flush=True) + + hdl_if_pkg = hdlsim_dvflow.mkTask("pyhdl-if.SvPkg") + hdl_if_dpi = hdlsim_dvflow.mkTask("pyhdl-if.DpiLib") + + gen_api = hdlsim_dvflow.mkTask( + "pyhdl-if.APIGenSV", + pkgname="datatypes_real_exp_pkg", + filename="datatypes_real_exp_pkg.sv", + modules=["datatypes_real_exp"], + pythonpath=[test_data_dir]) + + test_sv = hdlsim_dvflow.mkTask("std.FileSet", + base=test_data_dir, + include=["datatypes_real_exp.sv"], + type="systemVerilogSource") + + sim_img = hdlsim_dvflow.mkTask("hdlsim.%s.SimImage" % hdlsim_dvflow.sim, + top=["top"], + needs=[hdl_if_pkg, hdl_if_dpi, gen_api, test_sv]) + + sim_run = hdlsim_dvflow.mkTask( + "hdlsim.%s.SimRun" % hdlsim_dvflow.sim, + plusargs=["pyhdl_if_debug=1"], + needs=[sim_img]) + + status, out = hdlsim_dvflow.runTask(sim_run) + + assert status == 0 + + with open(os.path.join(out.output[0].basedir, "status.txt"), "r") as fp: + status = fp.read().strip() + assert "PASS:" in status and "FAIL:" not in status + +@pytest.mark.parametrize("hdlsim_dvflow", available_sims_dpi(excl=('xsm',)), indirect=True) +def test_real_imp(hdlsim_dvflow, hdl_if_env): + env = hdl_if_env + env["PYTHONPATH"] = test_data_dir + os.pathsep + env["PYTHONPATH"] + hdlsim_dvflow.setEnv(env) + + print("test_smoke_data_dir: %s" % test_data_dir, flush=True) + + hdl_if_pkg = hdlsim_dvflow.mkTask("pyhdl-if.SvPkg") + hdl_if_dpi = hdlsim_dvflow.mkTask("pyhdl-if.DpiLib") + + gen_api = hdlsim_dvflow.mkTask( + "pyhdl-if.APIGenSV", + pkgname="datatypes_real_imp_pkg", + filename="datatypes_real_imp_pkg.sv", + modules=["datatypes_real_imp"], + pythonpath=[test_data_dir]) + + test_sv = hdlsim_dvflow.mkTask("std.FileSet", + base=test_data_dir, + include=["datatypes_real_imp.sv"], + type="systemVerilogSource") + + sim_img = hdlsim_dvflow.mkTask("hdlsim.%s.SimImage" % hdlsim_dvflow.sim, + top=["top"], + needs=[hdl_if_pkg, hdl_if_dpi, gen_api, test_sv]) + + sim_run = hdlsim_dvflow.mkTask( + "hdlsim.%s.SimRun" % hdlsim_dvflow.sim, + plusargs=["pyhdl_if_debug=1"], + needs=[sim_img]) + + status, out = hdlsim_dvflow.runTask(sim_run) + + assert status == 0 + + with open(os.path.join(out.output[0].basedir, "status.txt"), "r") as fp: + status = fp.read().strip() + assert "PASS:" in status and "FAIL:" not in status + +@pytest.mark.parametrize("hdlsim_dvflow", available_sims_dpi(excl=('xsm',)), indirect=True) +def test_int_imp(hdlsim_dvflow, hdl_if_env): + env = hdl_if_env + env["PYTHONPATH"] = test_data_dir + os.pathsep + env["PYTHONPATH"] + hdlsim_dvflow.setEnv(env) + + print("test_smoke_data_dir: %s" % test_data_dir, flush=True) + + hdl_if_pkg = hdlsim_dvflow.mkTask("pyhdl-if.SvPkg") + hdl_if_dpi = hdlsim_dvflow.mkTask("pyhdl-if.DpiLib") + + gen_api = hdlsim_dvflow.mkTask( + "pyhdl-if.APIGenSV", + pkgname="datatypes_int_imp_pkg", + filename="datatypes_int_imp_pkg.sv", + modules=["datatypes_int_imp"], + pythonpath=[test_data_dir]) + + test_sv = hdlsim_dvflow.mkTask("std.FileSet", + base=test_data_dir, + include=["datatypes_int_imp.sv"], + type="systemVerilogSource") + + sim_img = hdlsim_dvflow.mkTask("hdlsim.%s.SimImage" % hdlsim_dvflow.sim, + top=["top"], + needs=[hdl_if_pkg, hdl_if_dpi, gen_api, test_sv]) + + sim_run = hdlsim_dvflow.mkTask( + "hdlsim.%s.SimRun" % hdlsim_dvflow.sim, + plusargs=["pyhdl_if_debug=1"], + needs=[sim_img]) + + status, out = hdlsim_dvflow.runTask(sim_run) + + assert status == 0 + + with open(os.path.join(out.output[0].basedir, "status.txt"), "r") as fp: + status = fp.read().strip() + assert "PASS:" in status and "FAIL:" not in status